协程如何优雅的处理异步回调

suspendCoroutine

在android中无处不在获取view的宽高,而获取宽高是需要在view绘制完后才能获取,所以这是一个时机问题,通常通过view.post来获取,那么用协程如何形如同步获取宽高呢?下面来试试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
GlobalScope.launch {
    val wh = getViewWh(view)
    Log.d(TAG, "width:${wh.first}")
    Log.d(TAG, "height:${wh.second}")
}
Log.d(TAG, "outer coroutine")

private suspend fun getViewWh(view: View) = suspendCoroutine<Pair<Int, Int>> { continuation ->
    view.post {
        val width = view.width
        val height = view.height
        continuation.resume(Pair(width, height))
    }
}

日志如下:

1
2
3
11:00:57.708  D  outer coroutine
11:00:57.810  D  width:1080
11:00:57.810  D  height:2206

使用了suspendCoroutine方法,方法的返回值,是suspendCoroutine指定的泛型。 可以看到日志正常获取,下面来看看字节码是如何实现的: 第一个suspendLambda.png launch启动的时候,内部的协程代码块编译结果是SuspendCoroutineActivity$onCreate$1: 第一个suspendLambda的invokeSuspend调用.png 调用了SuspendCoroutineActivity的静态方法access$getViewWh:

编译完后的静态方法.png 静态方法是直接调用了成员方法getViewWh,它是本次的重要实现:

getViewWh实现.png 传进来的Continuation对应了SuspendCoroutineActivity$onCreate$1,它是传给了SafeContinuation,此处注意到调用了continuation的intercepted方法,它是continuation的扩展方法,它是在continuationImpl中实现了,它实际是看context中是否有dispatcher,这块在协程切换线程中讲过,它实际是生成了一个CoroutineDispatcher。

接着看又调用了SuspendCoroutineActivity$getViewWh$2$1这个suspendLambda,并把SafeContinuation传进去了: 第二个suspendLambda.png 此处的Runnable就对应了post中的代码块,最终在run方法中调用了continuation的resumeWith方法,并把宽高回调出去了。此处的continuation是SafeContinuation,看下它的resumeWith方法: safeCoroutinuation的resumeWith实现.png 此处的result默认值是UNDECIDED: safeContinuation的result默认值.png 由于默认值是UNDECIDED,在上面getViewWh中先调用了safeContinuation的getOrThrow方法: safeContinuation的getOrThrow实现.png 所以会给result设置上了COROUTINE_SUSPENDED标记,所以在第一个suspendLambda的invokeSuspend方法中能被挂起,等到执行safeContinuation的resumeWith的时候,第一个suspendLambda就恢复了。最终获取到结果,整个流程就结束。

suspendCoroutine的回调中还有一个resumeWithException方法,用于返回失败的结果,如果返回失败的时候,需要捕捉异常。

suspendCancellableCoroutine

它和suspendCoroutine区别是回调中是一个CancellableContinuationImpl,它提供了cancel方法,当cancel的时候,程序不会崩溃。它会把cancel的结果分发到delegate中,此处的delegate其实就是对应了GlobalScope.launch启动时的suspendLambda,最后它会分发到父job中。看下CancellableContinuationImpl的cancel方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public override fun cancel(cause: Throwable?): Boolean {
    _state.loop { state ->
        if (state !is NotCompleted) return false // false if already complete or cancelling
        // Active -- update to final state
        val update = CancelledContinuation(this, cause, handled = state is CancelHandler || state is Segment<*>)
        if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
        // Invoke cancel handler if it was present
        when (state) {
            is CancelHandler -> callCancelHandler(state, cause)
            is Segment<*> -> callSegmentOnCancellation(state, cause)
        }
        // Complete state update
        detachChildIfNonResuable()
        dispatchResume(resumeMode) // no need for additional cancellation checks
        return true
    }
}

此处会构造出CancelledContinuation,它是CompletedExceptionally对象,这个其实就是协程异常的扩展类,最终把该对象设置到CancellableContinuationImpl的state上。 一路跟到DispatchedTask的dispatch方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
internal fun <T> DispatchedTask<T>.dispatch(mode: Int) {
    assert { mode != MODE_UNINITIALIZED } // invalid mode value for this method
    val delegate = this.delegate
    val undispatched = mode == MODE_UNDISPATCHED
    if (!undispatched && delegate is DispatchedContinuation<*> && mode.isCancellableMode == resumeMode.isCancellableMode) {
        // dispatch directly using this instance's Runnable implementation
        val dispatcher = delegate.dispatcher
        val context = delegate.context
        if (dispatcher.isDispatchNeeded(context)) {
            dispatcher.dispatch(context, this)
        } else {
            resumeUnconfined()
        }
    } else {
        // delegate is coming from 3rd-party interceptor implementation (and does not support cancellation)
        // or undispatched mode was requested
        resume(delegate, undispatched)
    }
}

此处会走dispatcher.dispatch逻辑,因为上面的GlobalScope.launch其实创建的dispatcher是一个Dispatcher.Default类型的。DispatchedTask是一个runnable接口,看下它的run方法: DispatchedTask的run方法处理异常.png 最终也是通过continuation的resumeWithException方法回调出去,然后交给了父job去处理,此时的exception是一个CancellationException,其实上面的suspendCoroutine也可以通过resumeWithException回调一个CancellationException,程序也不会崩溃。

参考:https://juejin.cn/post/7121517604644061192

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy