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实现,在此不进行具体分析。

相关推荐
楼田莉子13 分钟前
数据学习之队列
c语言·开发语言·数据结构·学习·算法
写不出来就跑路14 分钟前
SpringBoot静态资源与缓存配置全解析
java·开发语言·spring boot·spring·springboot
我命由我1234515 分钟前
Vue 开发问题:Missing required prop: “value“
开发语言·前端·javascript·vue.js·前端框架·ecmascript·js
企鹅侠客26 分钟前
Bash与Zsh与Fish:在Linux中你应该使用哪个Shell
linux·开发语言·bash·zsh·fish
RAY_010436 分钟前
Python—数据容器
开发语言·python
June bug39 分钟前
【python基础】python和pycharm的下载与安装
开发语言·python·pycharm
用户2018792831671 小时前
舞台剧兼职演员Dialog
android
参宿四南河三1 小时前
从Android实际应用场景出发,讲述RxJava3的简单使用
android·rxjava
扶我起来还能学_1 小时前
uniapp Android&iOS 定位权限检查
android·javascript·ios·前端框架·uni-app
每次的天空1 小时前
Android-重学kotlin(协程源码第二阶段)新学习总结
android·学习·kotlin