Kotlin協(xié)程

前言

?今年的Google開發(fā)者大會(huì)已表明將Kotlin作為其正式的語言,現(xiàn)Google大力主推Kotlin, 在GitHub上的官方Demo基本都是用Kotlin編寫的,在學(xué)習(xí)Kotlin的時(shí)候,覺得有必要把協(xié)程這部分單獨(dú)拎出來,現(xiàn)對(duì)Kotlin協(xié)程寫篇文章記錄。

Kotlin協(xié)程

?協(xié)程,即協(xié)作代碼段。相對(duì)線程而言,協(xié)程更適合于用來實(shí)現(xiàn)彼此熟悉的程序組件。協(xié)程提供了一種可以避免線程阻塞的能力,這就是它的核心功能。

?理解:子任務(wù)協(xié)作運(yùn)行,優(yōu)雅的處理異步問題解決方案

?協(xié)程的概念其實(shí)是很早就被提出的,下面引用BLOG中的一段回答來講解協(xié)程究竟是怎么來的:

1.一開始大家想要同一時(shí)間執(zhí)行多個(gè)代碼任務(wù),于是就有了并發(fā)。從程序員的角度可以看成是多個(gè)獨(dú)立的邏輯流,內(nèi)部可以是多CPU并行,也可以是單CPU時(shí)間分片。

2.但是一并發(fā)就有上下文切換的問題,干了一半跑去處理另一件事,我這做了一半的東西怎么保存。進(jìn)程就是這樣抽象出來的一個(gè)概念,搭配虛擬內(nèi)存、進(jìn)程表之類,用來管理獨(dú)立的程序運(yùn)行、切換。

3.后來硬件水平提升了,一臺(tái)電腦上有了好幾個(gè)CPU就可以一人跑一進(jìn)程,就是所謂的并行

4.但是一并行,進(jìn)程數(shù)一高,大部分系統(tǒng)資源就得用于進(jìn)程切換的狀態(tài)保存。后來搞出線程的概念,大致意思就是這個(gè)地方阻塞了,但我還有其他地方的邏輯流可以計(jì)算,不用特別麻煩的切換頁表,刷新TLB,只要把寄存器刷新一遍就行。

5.如果你嫌操作系統(tǒng)調(diào)度線程有不確定性,不知道什么時(shí)候開始,什么時(shí)候切走,我自己在進(jìn)程里面手寫代碼去管理邏輯調(diào)度這就是用戶態(tài)線程

6.而用戶態(tài)線程是不可剝奪的,如果一個(gè)用戶態(tài)線程發(fā)生了阻塞,就會(huì)造成整個(gè)進(jìn)程的阻塞,所以進(jìn)程需要自己擁有調(diào)度線程的能力。而如果用戶態(tài)線程將控制權(quán)交給進(jìn)程,讓進(jìn)程調(diào)度自己,這就是協(xié)程

后來我們的內(nèi)存越來越大,操作系統(tǒng)的調(diào)度也越來越智能,就慢慢沒人再去花時(shí)間去自己實(shí)現(xiàn)用戶態(tài)線程、協(xié)程這些東西了。

?協(xié)程把異步編程放入庫(kù)中來簡(jiǎn)化這類操作。程序邏輯在協(xié)程中順序表述,而底層的庫(kù)會(huì)將其轉(zhuǎn)換為異步操作。庫(kù)會(huì)將相關(guān)的用戶代碼打包成回調(diào),訂閱相關(guān)事件,調(diào)用其執(zhí)行到不同的線程(甚至不同的機(jī)器),而代碼依然像順序執(zhí)行那么簡(jiǎn)單。

為什么又要用協(xié)程了?

?既然上面說協(xié)程已經(jīng)淘汰在歷史的長(zhǎng)河中了,為什么現(xiàn)在又聲勢(shì)浩大的跑來了?
?前面我們講由于操作系統(tǒng)的多線程調(diào)度越來越智能,硬件設(shè)備也越來越好, 這大幅度提升了線程效率,因此正常情況下線程的效率是高于協(xié)程的,而且是遠(yuǎn)高于協(xié)程的。
?那么線程在什么情況下效率是最高的?就是在一直run的情況下。但是線程幾乎是很難一直run的,比如:線程上下文切換、負(fù)責(zé)計(jì)算阻塞、IO阻塞。
?于是又有人想起了協(xié)程,這個(gè)可以交給代碼調(diào)度的東西。

協(xié)程的本質(zhì)作用

?協(xié)程實(shí)際上就是極大程度的復(fù)用線程,通過讓線程滿載運(yùn)行,達(dá)到最大程度的利用CPU,進(jìn)而提升應(yīng)用性能。
?什么意思呢?

舉一個(gè)例子:在Android上發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求

step1:主線程創(chuàng)建一個(gè)網(wǎng)絡(luò)請(qǐng)求的任務(wù)。
step2:通過一個(gè)子線程去請(qǐng)求服務(wù)端響應(yīng)。
step2.1:等待網(wǎng)絡(luò)傳遞請(qǐng)求,其中可能包括了TCP/IP的一系列進(jìn)程。
step2.2:等待服務(wù)器處理,比如你請(qǐng)求一個(gè)列表數(shù)據(jù),服務(wù)器邏輯執(zhí)行依次去緩存、數(shù)據(jù)庫(kù)、默認(rèn)數(shù)據(jù)找到應(yīng)該返回給你的數(shù)據(jù),再將數(shù)據(jù)返回給你。
step2.3:又是一系列數(shù)據(jù)回傳。
step3:在子線程中獲取到服務(wù)器返回的數(shù)據(jù)。將數(shù)據(jù)轉(zhuǎn)換成想要的格式。
step4:在主線程中執(zhí)行某個(gè)回調(diào)方法。

?在上面例子中,第2步通常我們會(huì)用一個(gè)線程池存放一批創(chuàng)建好的線程做復(fù)用,防止多次創(chuàng)建線程。
?但是使用了線程池,就會(huì)遇到第一個(gè)問題,池中預(yù)存多少線程才最適合?存少了,后面的任務(wù)需要等待有空余的線程才能開始執(zhí)行;存多了,閑置的線程浪費(fèi)內(nèi)存。這個(gè)問題實(shí)際上海是線程利用率不高的問題。

上面的例子如果換做協(xié)程是這么個(gè)流程:

step1:主線程創(chuàng)建一個(gè)協(xié)程,在協(xié)程中創(chuàng)建網(wǎng)絡(luò)請(qǐng)求的任務(wù)。
step2:為協(xié)程分配一個(gè)執(zhí)行的線程(本例中就是子線程了),在線程中去請(qǐng)求服務(wù)端響應(yīng)。
step2.1:(接下來會(huì)發(fā)生阻塞),掛起子線程中的這個(gè)協(xié)程,等待網(wǎng)絡(luò)傳遞請(qǐng)求,其中可能包括了TCP/IP的一系列過程。
step2.2:協(xié)程依舊處理掛起狀態(tài),等待服務(wù)器處理,比如你請(qǐng)求一個(gè)列表數(shù)據(jù),服務(wù)器邏輯執(zhí)行依次去緩存、數(shù)據(jù)庫(kù)、默認(rèn)數(shù)據(jù)找到應(yīng)該返回給你的數(shù)據(jù),再將數(shù)據(jù)回傳給你。
step2.3:協(xié)程依舊處理掛起狀態(tài),又是一系列的數(shù)據(jù)回傳。
step3:獲取到服務(wù)器返回的數(shù)據(jù),在子線程中恢復(fù)掛起的協(xié)程。將數(shù)據(jù)轉(zhuǎn)換成想要的格式。
step4:在主線程中執(zhí)行某個(gè)回調(diào)方法。

?在上面的例子中,整個(gè)步驟沒有發(fā)生任何改變,但是因?yàn)橐肓藚f(xié)程概念。當(dāng)線程中的協(xié)程發(fā)生了掛起,線程依舊是可以繼續(xù)做事的,比如開始執(zhí)行第二個(gè)協(xié)程,而協(xié)程的掛起是一個(gè)很輕的操作(其內(nèi)在只是一次狀態(tài)機(jī)的變更,就是一個(gè)switch語句的分支執(zhí)行,詳細(xì)內(nèi)容后面有)。這就大大提升了多任務(wù)并發(fā)的效率,同時(shí)極大的提升了線程的利用率。

??這就是協(xié)程的本質(zhì)——極大程度的復(fù)用線程,通過讓線程滿載運(yùn)行,達(dá)到最大程度的利用CPU,進(jìn)而提升應(yīng)用性能

協(xié)程配置(以AS中為例)

?在app的build.gradle中增加如下配置:

kotlin {
    experimental {
        coroutines 'enable'
    }
}

?并添加如下依賴:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.20'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20'

配置截圖:


image.png

經(jīng)過上面的步驟Corutine的配置就已經(jīng)完成了。接下來就可以使用Coroutine了。

Kotlin協(xié)程使用

?在Kotlin上, 使用協(xié)程只需要知道兩個(gè)方法和他們的返回類型,就可以很熟悉的用上協(xié)程了。分別是:

fun launch(): Job
fun async(): Deferred

launch方法

?從方法名就能看出,launch表示啟動(dòng)一個(gè)協(xié)程。
下面是launch的源碼:

public fun launch(
    context: CoroutineContext = DefaultDispatcher,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    parent: Job? = null,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context, parent)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.initParentJob(newContext[Job])
    start(block, coroutine, coroutine)
    return coroutine
}

?launch()方法接收三個(gè)參數(shù),通常很少用到第二個(gè)參數(shù)。第一個(gè)參數(shù)是一個(gè)協(xié)程的上下文,CoroutineContext不僅可以用于在協(xié)程跳轉(zhuǎn)的時(shí)刻傳遞數(shù)據(jù),同時(shí)最主要的功能,是用于表明協(xié)程運(yùn)行與恢復(fù)時(shí)的上下文環(huán)境。
?通常Android在用的時(shí)候都是傳一個(gè)UI,就表示在UI線程啟動(dòng)協(xié)程,或者傳一個(gè)CommonPool表示在異步啟動(dòng)協(xié)程,還有一個(gè)Unconfined表示不指定,在哪個(gè)線程調(diào)用就在哪個(gè)線程恢復(fù)。

?下面貼出一個(gè)使用實(shí)例:

fun test(){
    
    val UI = HandlerContext(Handler(Looper.getMainLooper()) , "UI")
    launch (UI) {
        val isUIThread = Thread.currentThread() == Looper.getMainLooper().thread
        println("UI::==123=$isUIThread")
    }

    launch (CommonPool) {
        val isUIThread = Thread.currentThread() == Looper.getMainLooper().thread
        println("CommonPool::==456=$isUIThread")
    }
}

//輸出
UI::=123==true
CommonPool::==456=false

Job對(duì)象

?launch()方法會(huì)返回一個(gè)job對(duì)象,job對(duì)象常用的方法有三個(gè),叫startjoin , cannel。分別對(duì)應(yīng)了協(xié)程的啟動(dòng)、切換至當(dāng)前協(xié)程、取消。

start()方法使用實(shí)例:

fun test(){
    //當(dāng)啟動(dòng)類型設(shè)置成LAZY時(shí),協(xié)程不會(huì)立即啟動(dòng),而是手動(dòng)調(diào)用start()后它才會(huì)啟動(dòng)。
    val job = launch (UI , CoroutineStart.LAZY) {
        println("hello lazy")
    }
    job.start()
}

?join()方法就比較特殊,它是一個(gè)suspend方法。suspend修飾的方法(或閉包)只能調(diào)用被suspend修飾過的方法(或閉包)。方法聲明如下:

public suspend fun join()

?因此,join()方法只能在協(xié)程體內(nèi)部使用,跟它的功能:切換至當(dāng)前協(xié)程所吻合。

 fun test(){

    val job1 = launch (UI , CoroutineStart.LAZY) {

        println("launch test: hello1")

    }

    val job2 = launch (UI) {

        println("launch test: hello2")

        job1.join()

        println("launch test: hello3")

    }

}

//輸出

07-23 14:10:33.509 12607-12607/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out:launch test: hello2

07-23 14:10:33.614 12607-12607/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out: launch test: hello1

07-23 14:10:33.930 12607-12607/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out: launch test: hello3

async()方法

?async()方法也是創(chuàng)建一個(gè)協(xié)程并啟動(dòng),甚至連方法的聲明都跟launch()方法一模一樣。
?不同的是,方法的返回值,返回的是一個(gè)Deferred對(duì)象。這個(gè)接口是Job接口的子類。
?因此上文介紹的所有方法,都可以用于Deferred的對(duì)象。

Drferred最大的一個(gè)用處在于它特有的一個(gè)方法await()

public suspend fun await(): T

await()可以返回當(dāng)前協(xié)程的執(zhí)行結(jié)果,也就是你可以這樣寫代碼:

fun test(){

    val deferred1 = async(CommonPool){
        "heello1"
    }

    val deferred2 = async(UI){
        println("hello2")
        println(deferred1.await())
    }
}

?你發(fā)現(xiàn)神奇的地方了嗎?我讓一個(gè)工作在主線程的協(xié)程,獲取到了一個(gè)異步協(xié)程的返回值

?這意味著,我們以后網(wǎng)絡(luò)請(qǐng)求、圖片加載、文件操作說明的,都可以丟到一個(gè)異步的協(xié)程中去,然后在同步代碼中直接返回值,而不再需要去寫回調(diào)了。

?這就是我們經(jīng)常使用的一個(gè)最大的特性。

附上一個(gè)使用launch和async的簡(jiǎn)單例子:

fun test(){
    //每秒輸出兩個(gè)數(shù)字
    val job1 = launch (Unconfined , CoroutineStart.LAZY) {
        var count = 0
         while (true){
            count ++
            //delay()表示將這個(gè)協(xié)程掛起500ms
            delay(500)
            println("test job1: count::$count")
        }
    }

    //job2會(huì)立刻啟動(dòng)
    val job2 = async (CommonPool) {
        job1.start()
        "test job2: 第二個(gè)job2"
    }

    launch(UI){
        delay(3000)
        job1.cancel()
        //await()的規(guī)則是:如果此刻job2已經(jīng)執(zhí)行完則立刻返回結(jié)果,否則等待job2執(zhí)行
        println(job2.await())
    }
}

//最終輸出6次,job1就被cancel了
07-23 14:42:30.779 27525-27590/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out: test job1: count::1

07-23 14:42:31.281 27525-27590/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out: test job1: count::2

07-23 14:42:31.782 27525-27590/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out: test job1: count::3

07-23 14:42:32.283 27525-27590/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out: test job1: count::4

07-23 14:42:32.785 27525-27590/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out: test job1: count::5

07-23 14:42:33.286 27525-27590/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out: test job1: count::6

07-23 14:42:33.316 27525-27525/[cy.com.kotlindemo](http://cy.com.kotlindemo/) I/System.out: test job2: 第二個(gè)job2

?協(xié)程是通過編碼實(shí)現(xiàn)的一個(gè)任務(wù),它和操作系統(tǒng)或者JVM沒有任何關(guān)系,它的存在更類似于虛擬的線程。
?下面是一個(gè)示例:

val UI = HandlerContext(Handler(Looper.getMainLooper()) , "UI")
launch (UI) {
    folder.listFiles().filter{
        it.getName().endsWith(".png")
    }.forEach{
        val job = async (CommonPool) {
            getBitmap(it)
        }
        iamgeLayout.addImage(job.await())
    }
}

?Kotlin的語法會(huì)讓很多人覺得launch()async()是兩個(gè)協(xié)程方法。其實(shí)不然,真正的協(xié)程是launch()傳入的閉包參數(shù)。當(dāng)launch()調(diào)用的時(shí)候,會(huì)啟動(dòng)一個(gè)協(xié)程(本質(zhì)上并不一定是立即啟動(dòng),后面將會(huì)解釋)。
?async()方法調(diào)用的時(shí)候又啟動(dòng)了一個(gè)協(xié)程,此刻外部協(xié)程的狀態(tài)(包括CPU、方法調(diào)用、變量信息)會(huì)被暫存,進(jìn)而切換到async()啟動(dòng)的協(xié)程執(zhí)行。

?在上例中,launch()async()這兩個(gè)方法都顯式傳入了兩個(gè)參數(shù):

  1. 第一個(gè)參數(shù)是一個(gè)協(xié)程的上下文,類型是CoroutineContext
    ??CoroutineContext不僅可用于在協(xié)程跳轉(zhuǎn)的時(shí)刻傳遞數(shù)據(jù),同時(shí)最主要的功能,也是在本例中的作用是用于表明協(xié)程運(yùn)行與恢復(fù)時(shí)的上下文環(huán)境。
    例如launch()方法中的UI參數(shù),它實(shí)際上是一個(gè)封裝了HandleCoroutineContext對(duì)象。
val UI = HandlerContext(Handler(Looper.getMainLooper()) , "UI")

對(duì)應(yīng)的還有Swing,當(dāng)然在Android中是沒有這個(gè)對(duì)象的,但在Java工程中是有的:

object Swing : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor{
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = 
        SwingContinuation(continuation)
}
  1. 第二個(gè)參數(shù)是一個(gè)lambda表達(dá)式,也就是協(xié)程體。Kotlin語法:當(dāng)一個(gè)lambda是函數(shù)的最后一個(gè)參數(shù)的時(shí)候,lambda可以寫在圓括號(hào)外面

suspend修飾符

?suspend用于修飾會(huì)被暫停的函數(shù)。
?一個(gè)協(xié)程的方法(或閉包)必須被suspend修飾,同時(shí)suspend修飾的方法(或閉包)只能被suspend修飾過的方法(或閉包)調(diào)用。

?我們知道,Kotlin的閉包(lambda)在被編譯后是轉(zhuǎn)換成了內(nèi)部類對(duì)象,而一個(gè)被suspend修飾的閉包,就是一個(gè)特殊的內(nèi)部類了。例如下面的例子:

fun test(){
    launch {
        val job = async {
            "string"
        }
        println("=======${job.await()}")
    }
}

當(dāng)它被編譯以后,launch()傳入的閉包會(huì)被編譯成下面的樣子:

final class Main$test$1 extends CoroutineImpl implements Function2<CoroutineScope, Continuatin<? super Unit>, Object>{
    public final Continuation<Unit> create(@NotNull coroutineScope $receiver, @NotNull Continuaytion<? super Unit> continuation){
    }
    
    public final Object invoke(@NotNull CoroutineScope $receiver, @NotNull Continuation<? super Unit> continuation){
    }
    
    public final Object doResume(@Nullable Ojbect obj, @Nullable Throwable th){
    }
}

?而如果是一個(gè)普通方法被suspend修飾了以后,則只是會(huì)多出一個(gè)參數(shù),例如一個(gè)普通的test()無參內(nèi)容方法用suspend修飾了以后會(huì)被編譯成這樣:

public final Object test(Continuation<? super Unit> continuation){
    return Unit.INSTANCE;
}

?可以看到不論怎樣,都會(huì)具備一個(gè)Continuation的對(duì)象。而這個(gè)Continuation就是真正的Kotlin的協(xié)程。

協(xié)程的掛起與恢復(fù)

?理解了suspend做的事情后,再來看Kotlin的協(xié)程。上面的代碼中涉及到一個(gè)協(xié)程切換的情況。就是在launch()調(diào)用的時(shí)候,啟動(dòng)一個(gè)協(xié)程就是suspend修飾的閉包參數(shù)。在launch()啟動(dòng)協(xié)程內(nèi),async()又啟動(dòng)了一個(gè)協(xié)程。

實(shí)際上協(xié)程的切換,就是一個(gè)掛起當(dāng)前協(xié)程,啟動(dòng)新協(xié)程的過程。

協(xié)程啟動(dòng)流程

?掛起是指什么意思?首先要知道協(xié)程的啟動(dòng)流程。
launch()源碼是這樣的:

public fun launch(
    context: CoroutineContext = DefaultDispatcher,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    parent: Job? = null,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context, parent)
    val coroutine = if (start.isLazy)
    LazyStandaloneCoroutine(newContext, block) else
    StandaloneCoroutine(newContext, active = true)
    coroutine.initParentJob(newContext[Job])
    start(block, coroutine, coroutine)
    return coroutine
}

?我們看到聲明,start是一個(gè)枚舉對(duì)象,默認(rèn)值是DEFAULT,這里實(shí)際上是調(diào)用了枚舉的invoke()方法。
?我們?nèi)タ聪?strong>CoroutineStart這個(gè)枚舉類的源碼(關(guān)鍵部分):

public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>) =
when (this) {
    CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion)
    CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
    CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
    CoroutineStart.LAZY -> Unit // will start lazily
}

?看到CoroutineStart.DEFAULT啟動(dòng)了協(xié)程block.startCoroutineCancellable(receiver,completion),再看startCoroutineCancellable這個(gè)函數(shù):

internal fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>) =
createCoroutineUnchecked(completion).resumeCancellable(Unit)

?最終會(huì)調(diào)用createCoroutineUnchecked,這是一個(gè)擴(kuò)展方法,它的聲明如下:

@SinceKotlin("1.1")
@kotlin.jvm.JvmVersion
public fun <T> (suspend () -> T).createCoroutineUnchecked(
    completion: Continuation<T>
): Continuation<Unit> =
    if (this !is kotlin.coroutines.experimental.jvm.internal.CoroutineImpl)
        buildContinuationByInvokeCall(completion) {
            @Suppress("UNCHECKED_CAST")
            (this as Function1<Continuation<T>, Any?>).invoke(completion)
    }
    else
        (this.create(completion) as kotlin.coroutines.experimental.jvm.internal.CoroutineImpl).facade

?這段代碼中,通過判斷this是不是CoroutineImpl來做不同的操作。而this是什么?是一個(gè)有suspend修飾的閉包R.()->T,也就是前面launch()的參數(shù)傳入的閉包。

?還記得前面講過的suspend修飾的閉包在編譯后會(huì)變成什么嗎?剛好是一個(gè)CoroutineImpl類的對(duì)象。因此這里是調(diào)用了閉包的create()方法,最終將閉包創(chuàng)建成了Continuation對(duì)象并返回。
?這也驗(yàn)證了前面講的:Continuation就是真正的Kotlin的協(xié)程

?最后在創(chuàng)建好協(xié)程對(duì)象后,又會(huì)調(diào)用協(xié)程Continuationresume()方法(代碼在上面suspend修飾的的函數(shù)編譯后),而協(xié)程的resume()方法又會(huì)調(diào)用回編譯后suspend閉包轉(zhuǎn)換成的那個(gè)類里面的doResume方法(后面有介紹這里)。
?所以繞一圈又回來了。

協(xié)程的掛起

?明白了協(xié)程的啟動(dòng)流程以后,再來看掛起就清晰多了。我們看下面的代碼:

public final Object doResume(Object obj, Throwable th) {
    StringBuilder append;
    Object await;
    Deferred job;
    switch (this.label) {
        case 0:
            job = DeferredKt.async$default(null, null, (Function2) new 1(null), 3, null);
            append = new StringBuilder().append("========");
            this.L$0 = job;
            this.L$1 = append;
            this.label = 1;
            await = job.await(this);
            if (await == coroutine_suspended) {
                return coroutine_suspended;
            }
            break;
        case 1:
            StringBuilder stringBuilder = (StringBuilder) this.L$1;
            job = (Deferred) this.L$0;
            if (th == null) {
                append = stringBuilder;
                await = obj;
                break;
            }
            throw th;
        default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }
    System.out.println(append.append((String) await).toString());
    return Unit.INSTANCE;
}

?在switch()內(nèi)的lable變量,它就是標(biāo)識(shí)符,典型的狀態(tài)機(jī)設(shè)計(jì)。當(dāng)前在執(zhí)行的協(xié)程有多少個(gè)可能的狀態(tài),就會(huì)有多少位。

?首先看label為0時(shí)的代碼:

 job = DeferredKt.async$default(null, null, (Function2) new 1(null), 3, null);
            append = new StringBuilder().append("========");
            this.L$0 = job;
            this.L$1 = append;
            this.label = 1;
            await = job.await(this);
            if (await == coroutine_suspended) {
                return coroutine_suspended;
            }

?首先是job的聲明并返回job對(duì)象,它對(duì)應(yīng)的kotlin代碼是上面的:

val job = async{
    "string"
}

?接著是StringBuilder的append(),這個(gè)應(yīng)該就不用說了。
之后我們看到,有連個(gè)臨時(shí)的變量L$0L$1,它們時(shí)用來存儲(chǔ)當(dāng)前協(xié)程的臨時(shí)變量所生成的對(duì)象,由語法分析后判斷當(dāng)前協(xié)程只需要兩個(gè)臨時(shí)變量就能保存所有變量的信息了,所以就只生成了兩個(gè)。
?再之后,狀態(tài)就被設(shè)置為1了,表示即將進(jìn)入下一個(gè)協(xié)程了。
?job.await()啟動(dòng)了一個(gè)協(xié)程,這個(gè)方法返回了一個(gè)Objectcoroutine_suspended表示協(xié)程還在執(zhí)行,還沒有執(zhí)行完。

?因此這里的邏輯就是啟動(dòng)一個(gè)協(xié)程,如果這個(gè)協(xié)程是可以立即執(zhí)行完的,那就返回結(jié)果;否則直接return結(jié)束當(dāng)前方法,等待下一次狀態(tài)改變被觸發(fā),而這個(gè)結(jié)束當(dāng)前方法,處于等待的時(shí)刻,就是被掛起的時(shí)候。

內(nèi)部協(xié)程的切換

?在協(xié)程方法async()返回的是Deferred接口類型的對(duì)象,這個(gè)接口也繼承了Job接口,是它的子類。
在前面的例子中,async()返回的實(shí)際對(duì)象是DeferredCoroutine這個(gè)類的對(duì)象,它實(shí)現(xiàn)了Deferred接口,更重要的是,它實(shí)現(xiàn)了await()接口方法。還是看代碼:

@Suppress("UNCHECKED_CAST")
private open class DeferredCoroutine<T>(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<T>(parentContext, active), Deferred<T> {
    override fun getCompleted(): T = getCompletedInternal() as T
    suspend override fun await(): T = awaitInternal() as T
    override val onAwait: SelectClause1<T>
        get() = this as SelectClause1<T>
}

?await()其實(shí)是awaitInternal()的代理,它通過一個(gè)lock-free循環(huán),保證一定等到異常或者一個(gè)叫startInternal()的方法執(zhí)行完成才會(huì)返回。
?startInternal()方法的作用是在啟動(dòng)類型start=LAZY時(shí),保證協(xié)程初始化完成,所以在本例中是沒有意義的。在本例中有意義的是緊跟著這個(gè)方法后面調(diào)用的awaitSuspend()

protected suspend fun awaitInternal(): Any? {
// fast-path -- check state (avoid extra object creation)
    while(true) { // lock-free loop on state
        val state = this.state
        if (state !is Incomplete) {
        // already complete -- just return result
            if (state is CompletedExceptionally) throw state.exception
            return state

        }
        if (startInternal(state) >= 0) break // break unless needs to retry
    }
    return awaitSuspend() // slow-path
}

//------>

private suspend fun awaitSuspend(): Any? = suspendCancellableCoroutine { cont ->
    cont.disposeOnCompletion(invokeOnCompletion {
        val state = this.state
        check(state !is Incomplete)
        if (state is CompletedExceptionally)
            cont.resumeWithException(state.exception)
        else
            cont.resume(state)
        })
}

?這個(gè)方法中的cont就是調(diào)用await()時(shí)傳入的外部協(xié)程的對(duì)象。
?disposeOnCompletion()方法會(huì)調(diào)用invokeOnCompletion()方法返回的DisposableHandle對(duì)象的dispose()方法,去等待job中的內(nèi)容執(zhí)行完成。但如果job中的代碼在invokeOnCompletion()方法返回之前就已經(jīng)執(zhí)行完,就會(huì)返回一個(gè)NonDisposableHandle對(duì)象表示不需要再等待了。
?然后執(zhí)行閉包中的代碼,去根據(jù)job內(nèi)的代碼是否發(fā)生了異常去返回對(duì)應(yīng)的結(jié)果,這個(gè)結(jié)果就是state
?最終,又由外部協(xié)程cont調(diào)用了父類的resume()方法或者resumeWithException()方法(出異常時(shí))。

協(xié)程的恢復(fù)

?最終,與協(xié)程的啟動(dòng)流程中提及的一樣,Continationresume()方法會(huì)調(diào)用suspend閉包轉(zhuǎn)換成的類的doResume()方法。

override fun resume(value: Any?) {
    processBareContinuationResume(completion!!) {
        doResume(value, null)
    }
}

?而這里的參數(shù)value,就是協(xié)程在恢復(fù)時(shí)傳入的,內(nèi)部協(xié)程執(zhí)行后的結(jié)果。
這時(shí),看前面提及的狀態(tài)機(jī)中的label1的代碼:

  StringBuilder stringBuilder = (StringBuilder) this.L$1;
            job = (Deferred) this.L$0;
            if (th == null) {
                append = stringBuilder;
                await = obj;
                break;
            }
            throw th;

?至此,很清晰了,就是恢復(fù)之前掛起時(shí)保存起來的一系列變量的值,最后的if語句中的obj,就是前面子協(xié)程運(yùn)行后的結(jié)果傳遞到resume的參數(shù)中的value

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,957評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,081評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,485評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,720評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,263評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,025評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,204評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,461評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,945評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,205評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容

  • 本文主要介紹協(xié)程的用法, 以及使用協(xié)程能帶來什么好處. 另外, 也會(huì)粗略提一下協(xié)程的大致原理.本文的意義可能僅僅是...
    登高而望遠(yuǎn)閱讀 35,292評(píng)論 18 140
  • 這篇文章大部分內(nèi)容來自:https://github.com/Kotlin/kotlinx.coroutines/...
    Jason__Ding閱讀 19,946評(píng)論 9 55
  • 輕量級(jí)線程:協(xié)程 在常用的并發(fā)模型中,多進(jìn)程、多線程、分布式是最普遍的,不過近些年來逐漸有一些語言以first-c...
    Tenderness4閱讀 6,382評(píng)論 2 10
  • Kotlin語言基礎(chǔ)筆記 Kotlin流程控制語句筆記 Kotlin操作符重載與中綴表示法筆記 Kotlin擴(kuò)展函...
    dengyin2000閱讀 4,638評(píng)論 1 16
  • 前言 kotlin 現(xiàn)在都比較新鮮的一個(gè)語言。問過了身邊的朋友,有的似乎開始用其開始寫后臺(tái),有的開始用kotlin...
    yjy239閱讀 4,924評(píng)論 0 6