Kotlin協(xié)程

前言

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

Kotlin協(xié)程

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

step1:主線(xiàn)程創(chuàng)建一個(gè)網(wǎng)絡(luò)請(qǐng)求的任務(wù)开皿。
step2:通過(guò)一個(gè)子線(xiàn)程去請(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:在子線(xiàn)程中獲取到服務(wù)器返回的數(shù)據(jù)躏惋。將數(shù)據(jù)轉(zhuǎn)換成想要的格式幽污。
step4:在主線(xiàn)程中執(zhí)行某個(gè)回調(diào)方法。

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

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

step1:主線(xiàn)程創(chuàng)建一個(gè)協(xié)程,在協(xié)程中創(chuàng)建網(wǎng)絡(luò)請(qǐng)求的任務(wù)般卑。
step2:為協(xié)程分配一個(gè)執(zhí)行的線(xiàn)程(本例中就是子線(xiàn)程了)武鲁,在線(xiàn)程中去請(qǐng)求服務(wù)端響應(yīng)。
step2.1:(接下來(lái)會(huì)發(fā)生阻塞)蝠检,掛起子線(xiàn)程中的這個(gè)協(xié)程沐鼠,等待網(wǎng)絡(luò)傳遞請(qǐng)求,其中可能包括了TCP/IP的一系列過(guò)程。
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ù)国旷,在子線(xiàn)程中恢復(fù)掛起的協(xié)程矛物。將數(shù)據(jù)轉(zhuǎn)換成想要的格式。
step4:在主線(xiàn)程中執(zhí)行某個(gè)回調(diào)方法跪但。

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

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

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

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

kotlin {
    experimental {
        coroutines 'enable'
    }
}

?并添加如下依賴(lài):

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

配置截圖:


image.png

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

Kotlin協(xié)程使用

?在Kotlin上炒嘲, 使用協(xié)程只需要知道兩個(gè)方法和他們的返回類(lèi)型宇姚,就可以很熟悉的用上協(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線(xiàn)程啟動(dòng)協(xié)程,或者傳一個(gè)CommonPool表示在異步啟動(dòng)協(xié)程躲雅,還有一個(gè)Unconfined表示不指定鼎姊,在哪個(gè)線(xiàn)程調(diào)用就在哪個(gè)線(xiàn)程恢復(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è)此蜈,叫start即横、join , cannel噪生。分別對(duì)應(yīng)了協(xié)程的啟動(dòng)、切換至當(dāng)前協(xié)程东囚、取消跺嗽。

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

fun test(){
    //當(dāng)啟動(dòng)類(lèi)型設(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修飾過(guò)的方法(或閉包)璃吧。方法聲明如下:

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接口的子類(lèi)逮刨。
?因此上文介紹的所有方法,都可以用于Deferred的對(duì)象堵泽。

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

public suspend fun await(): T

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

fun test(){

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

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

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

?這意味著,我們以后網(wǎng)絡(luò)請(qǐng)求佳谦、圖片加載戴涝、文件操作說(shuō)明的,都可以丟到一個(gè)異步的協(xié)程中去,然后在同步代碼中直接返回值啥刻,而不再需要去寫(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é)程是通過(guò)編碼實(shí)現(xiàn)的一個(gè)任務(wù),它和操作系統(tǒng)或者JVM沒(méi)有任何關(guān)系映跟,它的存在更類(lèi)似于虛擬的線(xià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的語(yǔ)法會(huì)讓很多人覺(jué)得launch()async()是兩個(gè)協(xié)程方法努隙。其實(shí)不然球恤,真正的協(xié)程是launch()傳入的閉包參數(shù)。當(dāng)launch()調(diào)用的時(shí)候荸镊,會(huì)啟動(dòng)一個(gè)協(xié)程(本質(zhì)上并不一定是立即啟動(dòng)咽斧,后面將會(huì)解釋?zhuān)?br> ?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é)程的上下文盾剩,類(lèi)型是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中是沒(méi)有這個(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語(yǔ)法:當(dāng)一個(gè)lambda是函數(shù)的最后一個(gè)參數(shù)的時(shí)候番挺,lambda可以寫(xiě)在圓括號(hào)外面

suspend修飾符

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

?我們知道贴铜,Kotlin的閉包(lambda)在被編譯后是轉(zhuǎn)換成了內(nèi)部類(lèi)對(duì)象粪摘,而一個(gè)被suspend修飾的閉包瀑晒,就是一個(gè)特殊的內(nèi)部類(lè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()無(wú)參內(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做的事情后勤讽,再來(lái)看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é)程的過(guò)程作谚。

協(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è)枚舉類(lèi)的源碼(關(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

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

?還記得前面講過(guò)的suspend修飾的閉包在編譯后會(huì)變成什么嗎昵慌?剛好是一個(gè)CoroutineImpl類(lèi)的對(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è)類(lèi)里面的doResume方法(后面有介紹這里)侧蘸。
?所以繞一圈又回來(lái)了。

協(xié)程的掛起

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

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)該就不用說(shuō)了猜憎。
之后我們看到娩怎,有連個(gè)臨時(shí)的變量L$0L$1,它們時(shí)用來(lái)存儲(chǔ)當(dāng)前協(xié)程的臨時(shí)變量所生成的對(duì)象胰柑,由語(yǔ)法分析后判斷當(dāng)前協(xié)程只需要兩個(gè)臨時(shí)變量就能保存所有變量的信息了截亦,所以就只生成了兩個(gè)。
?再之后柬讨,狀態(tài)就被設(shè)置為1了崩瓤,表示即將進(jìn)入下一個(gè)協(xié)程了。
?job.await()啟動(dòng)了一個(gè)協(xié)程踩官,這個(gè)方法返回了一個(gè)Object却桶。coroutine_suspended表示協(xié)程還在執(zhí)行,還沒(méi)有執(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接口類(lèi)型的對(duì)象亏钩,這個(gè)接口也繼承了Job接口莲绰,是它的子類(lèi)。
在前面的例子中姑丑,async()返回的實(shí)際對(duì)象是DeferredCoroutine這個(gè)類(lèi)的對(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()的代理称龙,它通過(guò)一個(gè)lock-free循環(huán),保證一定等到異炒辽危或者一個(gè)叫startInternal()的方法執(zhí)行完成才會(huì)返回鲫尊。
?startInternal()方法的作用是在啟動(dòng)類(lèi)型start=LAZY時(shí),保證協(xié)程初始化完成沦偎,所以在本例中是沒(méi)有意義的疫向。在本例中有意義的是緊跟著這個(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)用了父類(lèi)的resume()方法或者resumeWithException()方法(出異常時(shí))。

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

?最終革为,與協(xié)程的啟動(dòng)流程中提及的一樣扭粱,Continationresume()方法會(huì)調(diào)用suspend閉包轉(zhuǎn)換成的類(lèi)的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í)保存起來(lái)的一系列變量的值嘉蕾,最后的if語(yǔ)句中的obj,就是前面子協(xié)程運(yùn)行后的結(jié)果傳遞到resume的參數(shù)中的value霜旧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末错忱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挂据,更是在濱河造成了極大的恐慌以清,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崎逃,死亡現(xiàn)場(chǎng)離奇詭異掷倔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)个绍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)勒葱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浪汪,“玉大人,你說(shuō)我怎么就攤上這事凛虽∷涝猓” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵凯旋,是天一觀的道長(zhǎng)呀潭。 經(jīng)常有香客問(wèn)我,道長(zhǎng)至非,這世上最難降的妖魔是什么钠署? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮睡蟋,結(jié)果婚禮上踏幻,老公的妹妹穿的比我還像新娘。我一直安慰自己戳杀,他們只是感情好该面,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著信卡,像睡著了一般隔缀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上傍菇,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天猾瘸,我揣著相機(jī)與錄音,去河邊找鬼丢习。 笑死牵触,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咐低。 我是一名探鬼主播揽思,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼见擦!你這毒婦竟也來(lái)了钉汗?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鲤屡,失蹤者是張志新(化名)和其女友劉穎损痰,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體酒来,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卢未,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了役首。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尝丐。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡显拜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出爹袁,到底是詐尸還是另有隱情远荠,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布失息,位于F島的核電站譬淳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盹兢。R本人自食惡果不足惜邻梆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绎秒。 院中可真熱鬧浦妄,春花似錦、人聲如沸见芹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)玄呛。三九已至阅懦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間徘铝,已是汗流浹背耳胎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惕它,地道東北人怕午。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像淹魄,于是被迫代替她去往敵國(guó)和親诗轻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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

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