Coroutine 學(xué)習(xí)(四)runBlocking async-await withContext 源碼分析
上篇文章主要分析了協(xié)程中的一個(gè)重要的部分,也就是CoroutineContext,以及它派生的子類CoroutineInterceptor和CoroutineDispatcher。提到CoroutineInterceptor就不能不提到Continuation,所以上篇文章也稍微說(shuō)了一下Continuation。 這篇文章,我想從源碼分析的視角,看看我們另外幾個(gè)常見(jiàn)的構(gòu)建協(xié)程代碼塊的api,它們分別是runBlocking async-await 和 withContext。?

runBlocking比較特別,方法和launch,async-await不一樣,并不是屬于CoroutineScope的擴(kuò)展方法。 這個(gè)方法設(shè)計(jì)的主要目的是作為非suspend風(fēng)格的代碼和suspend代碼的橋梁。
它本身的代碼非常短,但是有幾個(gè)注意的要點(diǎn):
runBlocking 會(huì)構(gòu)建自身的CoroutineDispatcher,也就是稍后會(huì)提及的EventLoop。它通過(guò)EventLoop做自己的事件調(diào)度。
EventLoop這個(gè)東西是存放在ThreadLocal里面的,也就是說(shuō)一個(gè)線程一個(gè)EventLoop。這個(gè)和Android本身的Looper是不是很像?
runBlocking構(gòu)建了一個(gè)特殊的coroutine的實(shí)例--BlockingCoroutine,它通過(guò)這個(gè)實(shí)例來(lái)支持自身的阻塞特性。
runBlocking的調(diào)用鏈和launch也是有相同的地方的,這種相同之處也體現(xiàn)在其他的coroutine builder中。只不過(guò)在上篇文章中,分析的重點(diǎn)是CoroutineInterceptor,所以上篇將createCoroutineUnintercepted() 這條鏈上出了interecpted()之外的方法全部都忽略了。這次我們來(lái)看看這部分的全貌

擴(kuò)展方法(suspend R.()->T).createCoroutineUnintercepted
Kotlin使用編譯器的某種方法 ,將suspend function 和 我們寫(xiě)在協(xié)程api中的block,我姑且稱為suspend lambda進(jìn)行轉(zhuǎn)化。這里我有一個(gè)不知道是否正確的猜想:雖然suspend function 和 suspend lambda 都會(huì)被轉(zhuǎn)化為Continuation,但是suspend lambda生成的對(duì)應(yīng)的類型是BaseContinuationImpl。
不過(guò)不論如何,返回的類型都是Continuation,接著會(huì)調(diào)用intercepted。intercepted具體的實(shí)現(xiàn)是從CoroutineContext中找到ContinuationInterceptor,這個(gè)類型的實(shí)例往往是我們傳入的CoroutineDispatcher。

仔細(xì)分析返回的DispathcedContinuation
?我們是在分析runBlocking的源碼,這個(gè)CoroutineDispatcher其實(shí)就是EventLoop。 在interecepted方法中,找到ContinuationInterceptor之后,就會(huì)調(diào)用其中的interceptContinuation。
通過(guò)CoroutineDispatcher#interceptContinuation會(huì)返回一個(gè)DispatchedContinuation對(duì)象。

這個(gè)DispatchedContinuation的兩個(gè)構(gòu)造器參數(shù)分別是當(dāng)前的Dispatcher,另外一個(gè)是Continuation。后者往上回溯代碼的話就可以發(fā)現(xiàn),其實(shí)是通過(guò)create操作轉(zhuǎn)換的suspend function/lambda。

在DispatcherContinuation#resumeWith方法中可以看出,具體的dispatch和isDispatchNeeded另外交給傳入的Dispatcher的實(shí)現(xiàn)負(fù)責(zé)。(上一篇文章中詳細(xì)的對(duì)這些代碼進(jìn)行分析?Continuation & CoroutineInterceptor & Dispatcher,可以參考一下)

這里之所以要強(qiáng)調(diào)上述過(guò)程中返回DispatchedContinuation,是因?yàn)檫@個(gè)對(duì)象在后面的執(zhí)行中有非常重要的作用。

在CoroutineInterceptor返回的DispatchedContinuation中,調(diào)用的dispatcher.dispatch具體實(shí)現(xiàn)是EventLoopBaseImpl#dispatch,它將DispatchedContinuation即時(shí)Continuation也是Runnable,所以dispatch可以將它加入了隊(duì)列中:
BlockingCoroutine#joinBlocking
我們現(xiàn)在重頭看一下runBlocking的代碼:
創(chuàng)建了BlockingCoroutine的實(shí)例之后,并執(zhí)行start,這個(gè)過(guò)程中并沒(méi)有進(jìn)行實(shí)質(zhì)上的阻塞操作,具體的阻塞操作藏在coroutine.joinBlocking中。這個(gè)方法將存儲(chǔ)在隊(duì)列中的DispatchedContinuation取出,并且執(zhí)行它的run方法(再次強(qiáng)調(diào),DispatchedContinuation也是一個(gè)Runnable)。
run方法就會(huì)取出DispatchedContinuation中的構(gòu)造器參數(shù)。上面已經(jīng)說(shuō)過(guò),continuation是包裝之后的suspend function/lambda,所以整個(gè)run方法就是和continuation#resumeWith有關(guān)。
最后我們來(lái)看看suspend lambda對(duì)應(yīng)的BaseContinuationImpl#resumeWith到底是如何進(jìn)行協(xié)程執(zhí)行的:
因?yàn)閰f(xié)程體中可以聲明子協(xié)程, 所以協(xié)程本身就具有遞歸的性質(zhì)。因此,這個(gè)方法主要將遞歸操作展平??梢岳斫鉃闃?shù)的遍歷操作,其中invokeSuspend方法是由編譯器生成的狀態(tài)機(jī)。

小結(jié)一下runBlocking是如何工作的:
創(chuàng)建coroutine對(duì)象,runBlocking中具體而言創(chuàng)建的是BlockingCoroutine。
執(zhí)行coroutine.start方法, 這個(gè)方法相當(dāng)重要,因?yàn)樗鼤?huì)執(zhí)行過(guò)程中會(huì)調(diào)用Kotlin編譯器生成的一些代碼,將我們寫(xiě)的suspend lambda/function轉(zhuǎn)化成continuation。轉(zhuǎn)換成的Continuation的實(shí)現(xiàn)是BaseContinuationImpl對(duì)象。這個(gè)continuation后續(xù)會(huì)被intercepted攔截,并且被包裝成DispatchedContinuation。DispatchedContinuation知道當(dāng)前的Dispatcher是什么,也持有Continuation對(duì)象。
DispatchedContinuation被放入隊(duì)列中,當(dāng)取出的時(shí)候會(huì)調(diào)用他的Continuation屬性的resumeWith,展開(kāi)遞歸。
那async-await和runBlocking有什么差異呢?

Async-await
async 和runBlocking 還有l(wèi)aunch也是比較相似的。默認(rèn)的啟動(dòng)邏輯都是CoroutineStart.DEFAULT,我們可以通過(guò)await獲取這個(gè)協(xié)程的返回結(jié)果。async會(huì)返回一個(gè)Deferred對(duì)象,Deferred是一個(gè)Job,這樣必然會(huì)遵守Job的生命周期:

同時(shí),在async中會(huì)創(chuàng)建一個(gè)DeferredCoroutine:
上面async的返回值是一個(gè)Deferred類型,因?yàn)镈eferredCoroutine是Deferred的實(shí)現(xiàn)類,所以上述代碼直接返回了DeferredCoroutine。DeferredCoroutine通過(guò)繼承JobSupport中的awaitInternal實(shí)現(xiàn)返回Continuation中完成的結(jié)果:

最后我們來(lái)看看withContext:
withContext會(huì)根據(jù)withContext中設(shè)置的context類型進(jìn)行判斷,假設(shè)Context不滿足在判斷中的兩個(gè)情況,就會(huì)構(gòu)造DispatchedCoroutine。因?yàn)镈ispatchedCoroutine持有構(gòu)造好的Continuation對(duì)象,所以當(dāng)withContext中的任務(wù)執(zhí)行完成之后,會(huì)調(diào)用afterCompletion回調(diào)繼續(xù)在原本的CoroutineContext中運(yùn)行:
總結(jié):
1. 不難看出 launch runBlocking async-await witContext這些協(xié)程API都具有相似的邏輯。在開(kāi)始都會(huì)創(chuàng)建一個(gè)AbstractCoroutine,在launch中是StandaloneCoroutine,在runBlocking中是BlockingCoroutine等等。這些Coroutine有著自己特殊的地方,比如BlockingCoroutine像隊(duì)列一樣,子任務(wù)會(huì)加入queue中等到取出執(zhí)行;DeferredCoroutine有著可以取出結(jié)果的能力。

2.?創(chuàng)建好了coroutine之后會(huì)調(diào)用createCoroutineUnintercepted,通過(guò)Kotlin 編譯器實(shí)現(xiàn)的某種方式將創(chuàng)建AbstractCoroutine作為參數(shù)(completion)作為createCoroutineUnintercepted的返回值,被攔截器捕獲。AbstractCoroutine會(huì)作為DispatchedContinuation的一部分,被對(duì)應(yīng)的Dispatcher進(jìn)行處理,什么之后做什么樣的處理,交給Dispatcher負(fù)責(zé)。

3. 在處理完成之后會(huì)調(diào)用Continuation的resumeWith,之后可能會(huì)發(fā)生切換到之前的Context(也就是調(diào)度器)的操作。這里的實(shí)現(xiàn)在BaseContinuationImpl中,會(huì)執(zhí)行由Kotin編譯器生成的自動(dòng)機(jī)(執(zhí)行invokeSuspend)方法。