前言
?今年的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'
配置截圖:
經(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ù):
- 第一個(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è)封裝了Handle的CoroutineContext對(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)
}
- 第二個(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é)程Continuation的resume()方法(代碼在上面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$0和L$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)流程中提及的一樣扭粱,Contination中resume()方法會(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霜旧。