Kotlin delay方法解析

本文记录了kotlin协程(Android)中delay方法的字节码实现,并解析了delay方法如何实现挂起操作。

一、delay方法介绍

1.1、delay方法使用举例

复制代码
class TestDelay {
    suspend fun testDelay() {
        Log.d("TestDelay", "before delay")
        delay(1000)
        Log.d("TestDelay", "after delay")
    }
}

上述方法将协程挂起1s,然后再恢复执行。

1.2、delay方法如何实现挂起操作

delay的挂起操作有别于Thread.sleep操作,delay挂起不会导致线程暂停执行,sleep会导致线程暂停执行。

查看上述代码的字节码反义结果如下:

delay方法正常执行情况下, 会返回一个COROUTINE_SUSPENDED对象,此对象与var4对象相同,会使当前的方法直接返回,从而起到挂起作用。

二、delay方法实现代码

接下查看delay是如何返回COROUTINE_SUSPENDED对象的

2.1、kotlin层代码

复制代码
public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

public interface Delay {

    /** @suppress **/
    @Deprecated(
        message = "Deprecated without replacement as an internal method never intended for public use",
        level = DeprecationLevel.ERROR
    ) // Error since 1.6.0
    public suspend fun delay(time: Long) {
        if (time <= 0) return // don't delay
        return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, it) }
    }

    public fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>)

    public fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
        DefaultDelay.invokeOnTimeout(timeMillis, block, context)
}

查看协程代码(1.7.3版本)发现,delay方法会调用协程context中的scheduleResumeAfterDelay方法,此方法会等待一段时间,然后再resume协程。

这里出现一个问题----delay方法没有返回值,这与1.2中的方法好像有些冲突,在下一节查看delay方法的字节码,看下具体实现是如何,来确认是否真的有冲突。

这里先看下suspendCancellableCoroutine方法,其实现如下:

复制代码
public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
        /*
         * For non-atomic cancellation we setup parent-child relationship immediately
         * in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but
         * properly supports cancellation.
         */
        cancellable.initCancellability()
        block(cancellable)
        cancellable.getResult()
    }


@SinceKotlin("1.3")
@InlineOnly
@Suppress("UNUSED_PARAMETER", "RedundantSuspendModifier")
public suspend inline fun <T> suspendCoroutineUninterceptedOrReturn(crossinline block: (Continuation<T>) -> Any?): T {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
    throw NotImplementedError("Implementation of suspendCoroutineUninterceptedOrReturn is intrinsic")
}

上图中的2个方法都用inline标注了,是内敛方法,所以在编译时会直接编译到delay方法中(注意:因为直接编译到delay方法中,所以内敛方法中最后一行的结果会被delay方法中的return使用)。同时看到在方法的最后调用了cancellable.getResult()方法,经查看代码发现,此方法内会返回COROUTINE_SUSPENDED对象。在此我们猜测此对象会作为delay方法的返回值,在下一节也会印证此猜测。

这样就了解了delay方法(编译后的字节码方法)的返回值对象是由何而来。针对kotlin源码中delay方法并没有返回值的问题,猜测在编译时delay方法会被编译成一个有返回值的方法,在下一节也会印证此猜测。

2.2、字节码

delay方法的字节码反义如下

复制代码
  @Nullable
  public static final Object delay(long timeMillis, @NotNull Continuation $completion) {
    if (timeMillis <= 0L)
      return Unit.INSTANCE; 
    int $i$f$suspendCancellableCoroutine = 0;
    Continuation uCont$iv = $completion;
    int $i$a$-suspendCoroutineUninterceptedOrReturn-CancellableContinuationKt$suspendCancellableCoroutine$2$iv = 0;
    CancellableContinuationImpl<? super Unit> cancellable$iv = new CancellableContinuationImpl(IntrinsicsKt.intercepted(uCont$iv), 1);
    cancellable$iv.initCancellability();
    CancellableContinuation<? super Unit> cont = cancellable$iv;
    int $i$a$-suspendCancellableCoroutine-DelayKt$delay$2 = 0;
    if (timeMillis < Long.MAX_VALUE)
      getDelay(cont.getContext()).scheduleResumeAfterDelay(timeMillis, cont); 
    if (cancellable$iv.getResult() == IntrinsicsKt.getCOROUTINE_SUSPENDED())
      DebugProbesKt.probeCoroutineSuspended($completion); 
    if (cancellable$iv.getResult() == 
      IntrinsicsKt.getCOROUTINE_SUSPENDED())
      return cancellable$iv.getResult(); 
    cancellable$iv.getResult();
    return Unit.INSTANCE;
  }

可以看出delay方法在经过编译之后,会变成一个返回值为object的方法,同时在正常执行情况下,会将cancellable$iv.getResult()作为返回值,而这个方法也就是kotlin中的cancellable.getResult()方法。

三、总结

本文介绍了delay方法会被编译成一个有返回值的方法,并通过返回COROUTINE_SUSPENDED对象的方式,实现挂起。在挂起后,会等待一段时间,然后再调用协程的resume方法,恢复协程执行。此恢复操作,如果是在主线程会通过handler.postDelay方式实现;如果是在子线程,会通过一个DelayedTask实现,在此不进行具体分析。

相关推荐
крон3 小时前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
zh_xuan3 小时前
c++ 单例模式
开发语言·c++·单例模式
老胖闲聊4 小时前
Python Copilot【代码辅助工具】 简介
开发语言·python·copilot
Blossom.1184 小时前
使用Python和Scikit-Learn实现机器学习模型调优
开发语言·人工智能·python·深度学习·目标检测·机器学习·scikit-learn
曹勖之4 小时前
基于ROS2,撰写python脚本,根据给定的舵-桨动力学模型实现动力学更新
开发语言·python·机器人·ros2
豆沙沙包?5 小时前
2025年- H77-Lc185--45.跳跃游戏II(贪心)--Java版
java·开发语言·游戏
军训猫猫头5 小时前
96.如何使用C#实现串口发送? C#例子
开发语言·c#
liuyang-neu5 小时前
java内存模型JMM
java·开发语言
我很好我还能学7 小时前
【面试篇 9】c++生成可执行文件的四个步骤、悬挂指针、define和const区别、c++定义和声明、将引用作为返回值的好处、类的四个缺省函数
开发语言·c++
蓝婷儿7 小时前
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
开发语言·python·学习