(接第一部分)
-
異常處理
1.協(xié)程的異常傳遞
協(xié)程的異常傳播也是遵循了協(xié)程上下文的機(jī)制弛车,除了取消異常(
CancellationException
)之外缸匪。當(dāng)一個(gè)協(xié)程有了異常担汤,如果沒(méi)有主動(dòng)捕獲異常睦擂,那么異常會(huì)雙向傳播贯钩,流程為:- 當(dāng)前協(xié)程出現(xiàn)異常
-
cancel
子協(xié)程 - 等待子協(xié)程
cancel
完成 - 傳遞給父協(xié)程并循環(huán)前面步驟
比如:
fun main () = runBlocking { val job1 = launch { delay(2000) println("job finished") } val job2 = launch { delay(1000) println("job2 finished") throw IllegalArgumentException() } delay(3000) println("finished") }
運(yùn)行結(jié)果:
job2 finished Exception in thread "main" java.lang.IllegalArgumentException at com.test.project.newgb.bluetooth.utils.TestKt$main$1$job2$1.invokeSuspend(Test.kt:393) ......
job2
1000ms后就發(fā)生了異常,導(dǎo)致job1
和父協(xié)程都直接退出
不同根協(xié)程的協(xié)程之間畜份,異常并不會(huì)自動(dòng)傳遞诞帐,比如:
fun main () = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = CoroutineScope(Dispatchers.IO).async{
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
println("new CoroutineScope finished")
}
delay(3000)
println("finished")
}
運(yùn)行結(jié)果:
job2 finished
job finished
finished
CancellationException
(取消異常):
CancellationException
會(huì)被 CoroutineExceptionHandler
忽略,但能被 try-catch
捕獲爆雹。
fun main () = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = launch {
delay(1000)
println("job2 finished")
}
job2.cancel() //job2取消息了停蕉,其實(shí)并沒(méi)有觸發(fā)CancellationException異常
delay(3000)
println("finished")
}
運(yùn)行結(jié)果:
job finished
finished
捕捉一下取消異常:
fun main () = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = launch {
try {
delay(1000)
println("job2 finished")
} catch (e:Exception) {
e.printStackTrace()
}
}
job2.cancel()
delay(3000)
println("finished")
}
運(yùn)行結(jié)果:
job finished
finished
奇怪,并沒(méi)有發(fā)生異常顶别,什么原因呢谷徙?
因?yàn)榭扇∠膾炱鸷瘮?shù)會(huì)在取消時(shí)拋出CancellationException
,上面delay(1000)
會(huì)在取消時(shí)拋出CancellationException
驯绎,但是上面的代碼中 delay(1000)
并沒(méi)有執(zhí)行完慧,因?yàn)閰f(xié)程還沒(méi)有開(kāi)始執(zhí)行就被 cancel
了
上面的例子稍加修改:
fun main () = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = launch {
try {
delay(1000)
println("job2 finished")
} catch (e:Exception) {
e.printStackTrace()
}
}
delay(500) //延遲500毫秒,讓job2處于delay狀態(tài)
job2.cancel()
delay(3000)
println("finished")
}
運(yùn)行結(jié)果:
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@184f6be2
job finished
finished
去掉捕捉異常:
fun main () = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = launch {
delay(1000)
println("job2 finished")
}
delay(500)//延遲500毫秒剩失,讓job2處于delay狀態(tài)
job2.cancel()
delay(3000)
println("finished")
}
運(yùn)行結(jié)果:
job finished
finished
為什么沒(méi)有拋出異常呢屈尼?
因?yàn)?code>kotlin的協(xié)程是這樣規(guī)定的:CancellationException
這個(gè)異常是被視為正常現(xiàn)象的取消拴孤。協(xié)程在內(nèi)部使用 CancellationException
來(lái)進(jìn)行取消脾歧,所有處理程序都會(huì)忽略這類異常,因此它們僅用作調(diào)試信息的額外來(lái)源演熟,這些信息可以用 catch
塊捕獲鞭执。
如果不希望協(xié)程內(nèi)的異常向上傳播或影響同級(jí)協(xié)程∶⒋猓可以使用 SupervisorJob
協(xié)程的上下文為SupervisorJob
時(shí)兄纺,該協(xié)程中的異常不會(huì)向外傳播,因此不會(huì)影響其父親/兄弟協(xié)程化漆,也不會(huì)被其兄弟協(xié)程拋出的異常影響
我們常見(jiàn)的 MainScope
估脆、viewModelScope
、lifecycleScope
都是用 SupervisorJob()
創(chuàng)建的座云,所以這些作用域中的子協(xié)程異常不會(huì)導(dǎo)致根協(xié)程退出
正確使用SupervisorJob
的方法:
// job1疙赠、job2、job3和job4的父Job都是SupervisorJob
val scope = CoroutineScope(SupervisorJob())
job1 = scope.launch {...}
job2 = scope.launch {...}
supervisorScope {
job3 = launch {...}
job4 = launch {...}
}
而不是采用launch(SupervisorJob()){...}
這種方式(launch
生成的協(xié)程的父job
為SupervisorJob
朦拖,其大括號(hào)內(nèi)部的job
依然是普通Job
)
比如修改一下第一個(gè)例子:
fun main () = runBlocking {
val scope = CoroutineScope(SupervisorJob())
scope.launch {
val job1 = scope.launch {
delay(2000)
println("job finished")
}
val job2 = scope.launch {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
delay(2000)
println("parent job finished")
}
delay(3000)
println("finished")
}
運(yùn)行結(jié)果:
job2 finished
Exception in thread "DefaultDispatcher-worker-3" java.lang.IllegalArgumentException
at com.test.project.newgb.bluetooth.utils.TestKt$main$1$1$job2$1.invokeSuspend(Test.kt:396)
......
job finished
parent job finished
finished
雖然job2
發(fā)生發(fā)異常圃阳,但是并沒(méi)有影響job1
和父協(xié)程
但是如果采用不正確的方式,比如:
fun main() = runBlocking{
val grandfatherJob = SupervisorJob()
//創(chuàng)建一個(gè)Job璧帝,
val job = launch(grandfatherJob) {
//啟動(dòng)一個(gè)子協(xié)程
val childJob1 = launch {
println("childJob1 start")
delay(1000)
throw IllegalArgumentException()
println("childJob1 end")
}
val childJob2 = launch {
println("childJob2 start")
delay(2000)
println("childJob2 end")
}
}
delay(3000)
println("end")
}
運(yùn)行結(jié)果:
childJob1 start
childJob2 start
Exception in thread "main" java.lang.IllegalArgumentException
......
end
可以看出childJob1
的異常影響了childJob2
捍岳,并沒(méi)有阻止異常的傳遞,主要就是SupervisorJob
的使用方式不對(duì)。
GlobalScope.launch
方式啟動(dòng)的是頂層協(xié)程祟同,本身不存在父協(xié)程,在里面發(fā)生異常后理疙, 只會(huì)在logCat
輸出異常異常晕城,并不會(huì)影響到外部線程的運(yùn)行,比如:
fun main() = runBlocking {
println("start")
GlobalScope.launch {
println("launch Throwing exception")
throw NullPointerException()
}
Thread.sleep(3000)
//GlobalScope.launch產(chǎn)生的異常不影響該線程執(zhí)行
println("end")
}
運(yùn)行結(jié)果:
start
launch Throwing exception
Exception in thread "DefaultDispatcher-worker-1" java.lang.NullPointerException
at com.test.project.newgb.bluetooth.utils.TestKt$main$1$1.invokeSuspend(Test.kt:511)
......
end
再比如:
fun main() = runBlocking {
println("start")
launch {
GlobalScope.launch {
println("launch Throwing exception")
throw NullPointerException()
}
delay(1000)
println("out launch end")
}
delay(3000)
//GlobalScope.launch產(chǎn)生的異常不影響該線程執(zhí)行
println("end")
}
運(yùn)行結(jié)果:
start
launch Throwing exception
Exception in thread "DefaultDispatcher-worker-1" java.lang.NullPointerException
at com.test.project.newgb.bluetooth.utils.TestKt$main$1$1$1.invokeSuspend(Test.kt:513)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
......
out launch end
end
那GlobalScope.async
呢窖贤?與GlobalScope.launch
是不同的砖顷,因?yàn)?code>GlobalScope.async在使用await()
方法時(shí)會(huì)拋出異常,比如:
fun main() = runBlocking {
println("start")
val job = GlobalScope.async {
println("launch Throwing exception")
throw NullPointerException()
}
job.join()//采用join
println("end")
}
輸出:
start
launch Throwing exception
end
將join
改為await
fun main() = runBlocking {
println("start")
val job = GlobalScope.async {
println("launch Throwing exception")
throw NullPointerException()
}
job.await()//采用await
println("end")
}
輸出:
start
launch Throwing exception
Exception in thread "main" java.lang.NullPointerException
at com.test.project.newgb.bluetooth.utils.TestKt$main$1$job$1.invokeSuspend(Test.kt:511)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
......
可以看出GlobalScope.async
出現(xiàn)異常后赃梧,當(dāng)使用了await
時(shí)滤蝠,還是會(huì)影響外部線程。
2.協(xié)程的異常處理
launch:
通過(guò)launch
啟動(dòng)的異呈卩郑可以通過(guò)try-catch
來(lái)進(jìn)行異常捕獲物咳,或者使用協(xié)程封裝的拓展函數(shù)runCatching
來(lái)捕獲(其內(nèi)部也是使用的try-catch
),還可以使用CoroutineExceptionHandler
對(duì)異常進(jìn)行統(tǒng)一處理蹄皱,這也更符合結(jié)構(gòu)化并發(fā)原則览闰。
使用try-catch
時(shí),要注意:不要用try-catch
直接包裹launch巷折、async
使用CoroutineExceptionHandler
捕獲異常需要滿足:
CoroutineExceptionHandler
需要存在于 CoroutineScope
的 CoroutineContext
中压鉴,或者在 CoroutineScope
或者 supervisorScope
創(chuàng)建的直接子協(xié)程中。
采用try-catch
的例子:
fun main() = runBlocking {
val scope = CoroutineScope(Job())
scope.launch {
try {
throw NullPointerException("a exception")
} catch(e: Exception) {
println("handle exception : ${e.message}")
}
}
delay(1000)
}
輸出:
handle exception : a exception
or
fun main() = runBlocking {
val scope = CoroutineScope(Job())
scope.launch {
runCatching {
throw NullPointerException("a exception")
}.onFailure {
println("handle exception : ${it.message}")
}
}
delay(1000)
}
輸出:
handle exception : a exception
如果直接用try-catch
包裹launch
:
fun main() = runBlocking {
val scope = CoroutineScope(Job())
try {
scope.launch {
throw NullPointerException("a exception")
}
} catch(e: Exception) {
println("handle exception : ${e.message}")
}
delay(1000)
}
輸出:
Exception in thread "DefaultDispatcher-worker-1" java.lang.NullPointerException: a exception
at com.test.project.newgb.bluetooth.utils.TestKt$main$1$1.invokeSuspend(Test.kt:530)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
......
可以發(fā)現(xiàn)锻拘,異常并沒(méi)有被捕獲油吭,所以要將try-catch
放到協(xié)程體內(nèi)部
采用CoroutineExceptionHandler
的例子:
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job()+handleException)
scope.launch {
throw NullPointerException("a exception")
}
delay(1000)
}
輸出:
CoroutineExceptionHandler catch java.lang.NullPointerException: a exception
or
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job())
scope.launch(handleException) {
throw NullPointerException("a exception")
}
delay(1000)
}
輸出:
CoroutineExceptionHandler catch java.lang.NullPointerException: a exception
如果改為這樣呢:
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job())
scope.launch {
launch(handleException) {
throw NullPointerException("a exception")
}
}
delay(1000)
}
輸出:
Exception in thread "DefaultDispatcher-worker-2" java.lang.NullPointerException: a exception
at com.test.project.newgb.bluetooth.utils.TestKt$main$1$1$1.invokeSuspend(Test.kt:536)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
......
因?yàn)?code>CoroutineExceptionHandler使用的位置不對(duì),所以并沒(méi)有發(fā)揮作用
再修改一下:
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job())
scope.launch(handleException) {
launch {
throw NullPointerException("a exception")
}
}
delay(1000)
}
輸出:
CoroutineExceptionHandler catch java.lang.NullPointerException: a exception
所以要注意使用CoroutineExceptionHandler
捕獲異常時(shí)需要滿足的條件署拟。
async:
當(dāng)
async
開(kāi)啟的協(xié)程為根協(xié)程(由協(xié)程作用域直接管理的協(xié)程) 或supervisorScope
的直接子協(xié)程時(shí)婉宰,異常不會(huì)主動(dòng)拋出,異常在調(diào)用await
時(shí)拋出芯丧,使用try-catch
可以捕獲異常當(dāng)
async
開(kāi)啟的協(xié)程為根協(xié)程(由協(xié)程作用域直接管理的協(xié)程) 或supervisorScope
的直接子協(xié)程時(shí)芍阎,如果不調(diào)用await
,異常不會(huì)主動(dòng)拋出缨恒,同時(shí)產(chǎn)生的異常不影響外部線程執(zhí)行(相當(dāng)于內(nèi)部消化了谴咸,但是協(xié)程內(nèi)部異常發(fā)生后的代碼不會(huì)再執(zhí)行)當(dāng)
async
開(kāi)啟的協(xié)程不為根協(xié)程(不由協(xié)程作用域直接管理的協(xié)程) 同時(shí)也不是supervisorScope
的直接子協(xié)程時(shí),異常發(fā)生時(shí)會(huì)立即拋出骗露,此時(shí)可以用try-catch
或者CoroutineExceptionHandler
捕獲并攔截異常如果發(fā)生了嵌套岭佳,比如多個(gè)
async
或launch
嵌套,前面三條仍然成立萧锉,即主要看根協(xié)程是否為async
所開(kāi)啟珊随,因?yàn)樽訁f(xié)程的異常會(huì)一直向上傳遞給父協(xié)程,所以要把async
開(kāi)啟的協(xié)程內(nèi)部看成一個(gè)整體
根協(xié)程(由協(xié)程作用域直接管理的協(xié)程) ,await
時(shí)拋出異常:
比如:
fun main() = runBlocking {
//async 開(kāi)啟的協(xié)程為根協(xié)程
val deferred = GlobalScope.async {
throw Exception()
}
try {
deferred.await() //拋出異常
} catch (t: Throwable) {
println("捕獲異常:$t")
}
}
輸出:
捕獲異常:java.lang.Exception
supervisorScope
的直接子協(xié)程叶洞,await
時(shí)拋出異常:
fun main() = runBlocking {
supervisorScope {
//async 開(kāi)啟的協(xié)程為 supervisorScope 的直接子協(xié)程
val deferred = async {
throw Exception()
}
try {
deferred.await() //拋出異常
} catch (t: Throwable) {
println("捕獲異常:$t")
}
}
}
輸出:
捕獲異常:java.lang.Exception
根協(xié)程(由協(xié)程作用域直接管理的協(xié)程) 鲫凶,不用await
:
fun main() = runBlocking {
//async 開(kāi)啟的協(xié)程為根協(xié)程
val deferred = GlobalScope.async {
println("async a coroutine")
throw Exception()
}
try {
deferred.join()
} catch (t: Throwable) {
println("捕獲異常:$t")
}
delay(1000)
println("end")
}
輸出:
async a coroutine
end
上面并沒(méi)有捕捉到異常,外部的線程也沒(méi)有被影響
supervisorScope
的直接子協(xié)程衩辟,不用await
fun main() = runBlocking {
supervisorScope {
//async 開(kāi)啟的協(xié)程為 supervisorScope 的直接子協(xié)程
val deferred = async {
println("async a coroutine")
throw Exception()
}
try {
deferred.join()
} catch (t: Throwable) {
println("捕獲異常:$t")
}
}
delay(1000)
println("end")
}
輸出:
async a coroutine
end
上面并沒(méi)有捕捉到異常螟炫,外部的線程也沒(méi)有被 影響
如果是同一線程呢,比如:
override fun onCreate(savedInstanceState: Bundle?) {
......
test()
println("ddd test end")
}
fun test() {
MainScope().launch {
//async 開(kāi)啟的協(xié)程為協(xié)程作用域直接管理的協(xié)程
val deferred = GlobalScope.async(Dispatchers.Main) {
println("ddd async a coroutine thread:${Thread.currentThread().name}")
throw Exception()
}
try {
deferred.join()
} catch (t: Throwable) {
println("ddd 捕獲異常:$t")
}
delay(1000)
println("ddd end thread:${Thread.currentThread().name}")
}
}
輸出:
ddd test end
ddd async a coroutine thread:main
ddd end thread:main
可看出async
在主線程發(fā)生了異常艺晴,但是沒(méi)有影響主線程的執(zhí)行昼钻,把deferred.join()
去掉結(jié)果也一樣
其它情況,異常會(huì)在發(fā)生時(shí)立刻拋出并傳播封寞,需要在異常發(fā)生的地方進(jìn)行捕捉然评,比如:
fun main() = runBlocking {
val deferred = async {
try {
println("async a coroutine")
throw Exception()
} catch (t: Throwable) {
println("捕獲異常:$t")
}
}
deferred.await() //不能在此捕捉,雖然能捕捉到異常狈究,但是無(wú)法阻止異常的傳播
delay(1000)
println("end")
}
輸出:
async a coroutine
捕獲異常:java.lang.Exception
end
使用CoroutineExceptionHandler
:
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job() + handleException )
val deferred = scope.async {
println("async a coroutine")
throw Exception()
}
deferred.await()
delay(1000)
println("end")
}
輸出:
async a coroutine
Exception in thread "main" java.lang.Exception
at com.test.project.newgb.bluetooth.utils.TestKt$main$1$deferred$1.invokeSuspend(Test.kt:627)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
......
奇怪碗淌,并沒(méi)有捕捉到異常,為什么抖锥?可以再回頭看看async
的第一條贯莺,原來(lái)是因?yàn)?code>async產(chǎn)生了頂級(jí)協(xié)程,只能在await
時(shí)捕捉宁改,改一下:
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job() + handleException )
//由于為頂級(jí)協(xié)程缕探,故CoroutineExceptionHandler不會(huì)起作用
val deferred = scope.async {
println("async a coroutine")
throw Exception()
}
try {
deferred.await() //async頂級(jí)協(xié)程需要在此捕捉
} catch (t: Throwable) {
println("捕獲異常:$t")
}
delay(1000)
println("end")
}
發(fā)生嵌套的情況
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job() + handleException )
val deferred = scope.async {
async {
launch {
println("async a coroutine")
throw Exception()
}
}
}
//下面的try-catch所有代去掉,將不會(huì)產(chǎn)生異常
try {
deferred.await()
} catch (t: Throwable) {
println("捕獲異常:$t")
}
delay(1000)
println("end")
}
輸出:
async a coroutine
捕獲異常:java.lang.Exception
end
可以看出CoroutineExceptionHandler
并沒(méi)有起作用还蹲,異常只會(huì)在deferred.await()
拋出爹耗,同時(shí)只能在此捕獲,原因就是協(xié)程作用域直接管理async
谜喊,符合第一條
去掉try-catch
潭兽,驗(yàn)證一下:
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job() + handleException )
val deferred = scope.async {
async {
launch {
println("async a coroutine")
throw Exception()
}
}
}
delay(1000)
println("end")
}
輸出:
async a coroutine
end
此時(shí)并沒(méi)有拋出異常
將最外層改為launch
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job() + handleException )
val job = scope.launch {
async {
launch {
println("async a coroutine")
throw Exception()
}
}
}
delay(1000)
println("end")
}
輸出:
async a coroutine
CoroutineExceptionHandler catch java.lang.Exception
end
可以看出拋出了異常,而且被CoroutineExceptionHandler
捕獲
我們說(shuō)過(guò)斗遏,要把async
下面的看成一個(gè)整體山卦,可以驗(yàn)證一下:
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job() + handleException )
val deferred = scope.async {
async {
val deferred2 = async {
println("async a coroutine")
throw Exception()
}
println("async in await")
deferred2.await() //去掉這條代碼,也一樣不會(huì)拋出異常
println("async in end") //不會(huì)執(zhí)行诵次,因?yàn)閳?zhí)行前出現(xiàn)異常
}
println("async out delay start")
delay(1000)
println("async out end") //不會(huì)執(zhí)行账蓉,因?yàn)閳?zhí)行前出現(xiàn)異常
}
delay(1000)
println("end")
}
輸出:
async out delay start
async in await
async a coroutine
end
可以看出并沒(méi)有拋出異常,因?yàn)樽罱K要交給最外層的async
來(lái)處理逾一,里面的子協(xié)程自行處理并沒(méi)有用铸本。
但是要注意一點(diǎn),雖然沒(méi)有拋出異常遵堵,但是異常發(fā)生后箱玷,async
里面異常發(fā)生點(diǎn)后面的代碼是不會(huì)執(zhí)行的
給最外層async
加await
fun main() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
println("CoroutineExceptionHandler catch $throwable")
}
val scope = CoroutineScope(Job() + handleException )
val deferred = scope.async {
async {
val deferred2 = async {
println("async a coroutine")
throw Exception()
}
println("async in await")
deferred2.await()
println("async in end")
}
println("async out delay start")
delay(1000)
println("async out end")
}
deferred.await() //加上try-catch后能捕捉并攔截異常
delay(1000)
println("end")
}
輸出:
async out delay start
async in await
async a coroutine
Exception in thread "main" java.lang.Exception
at com.test.project.newgb.bluetooth.utils.TestKt$main$1$deferred$1$1$deferred2$1.invokeSuspend(Test.kt:629)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
......
這符合第一條
三怨规、協(xié)程代碼執(zhí)行順序
上面講到異步代碼能按順序執(zhí)行,同步代碼又可以不按順序執(zhí)行锡足,那協(xié)程代碼的執(zhí)行順序到底是什么規(guī)律呢波丰?這個(gè)問(wèn)題也是我學(xué)習(xí)協(xié)程時(shí)最納悶的一點(diǎn),搞不清楚什么時(shí)候會(huì)順序執(zhí)行舶得,什么時(shí)候又掛起當(dāng)前的代碼去執(zhí)行其它代碼呀舔;什么時(shí)候會(huì)等待函數(shù)執(zhí)行完成,什么時(shí)候又不等待函數(shù)執(zhí)行完成扩灯。
要掌握協(xié)程代碼執(zhí)行順序的規(guī)律,必須要明白Suspend function
预茄。
-
什么是 Suspend function
Suspend function
是用suspend
關(guān)鍵字修飾的函數(shù)鳞疲,suspend function
需要在協(xié)程中執(zhí)行材义,或者在另一個(gè)suspend function
內(nèi)之所以叫掛起函數(shù),是因?yàn)閽炱鸷瘮?shù)具備掛起恢復(fù)協(xié)程的能力捻撑,即掛起函數(shù)可以將其所在的協(xié)程掛起,然后在掛起后能恢復(fù)協(xié)程繼續(xù)執(zhí)行(注意:掛起的是整個(gè)協(xié)程缤底,而不是掛起函數(shù)本身)
掛起函數(shù)并不一定會(huì)掛起協(xié)程顾患,要掛起協(xié)程需要在它內(nèi)部直接或間接調(diào)用
Kotlin
自帶的suspend
函數(shù),比如你的掛起函數(shù)里只是print
一條日志个唧,并不會(huì)起到掛起的作用江解,所以suspend
關(guān)鍵字只是一個(gè)提醒,提醒協(xié)程框架徙歼,函數(shù)里面會(huì)有耗時(shí)操作犁河,如果并沒(méi)有,將和普通函數(shù)一樣掛起函數(shù)掛起協(xié)程屬于'非阻塞式'掛起魄梯,即不會(huì)阻塞協(xié)程所在線程桨螺,當(dāng)協(xié)程掛起時(shí),線程會(huì)越過(guò)協(xié)程繼續(xù)執(zhí)行
因?yàn)閰f(xié)程能切換線程酿秸,所以協(xié)程被掛起有兩種情況:協(xié)程暫停運(yùn)行灭翔、協(xié)程被切換到其它線程上運(yùn)行
掛起函數(shù)執(zhí)行完畢后,協(xié)程從掛起函數(shù)之后的代碼恢復(fù)執(zhí)行辣苏,線程會(huì)切回到協(xié)程之前的線程(除
Dispatchers.Unconfined
外) -
掛起函數(shù)舉例
override fun onCreate(savedInstanceState: Bundle?) { ...... test() println("test end") } fun test() { MainScope().launch { println("MainScope().launch start thread is:${Thread.currentThread().name}") delay(5000) //掛起函數(shù)肝箱,會(huì)將MainScope().launch生成的協(xié)程掛起,后面的代碼5秒后執(zhí)行,UI線程繼續(xù)執(zhí)行 println("launch start thread is:${Thread.currentThread().name}") launch { println("launch thread1 is:${Thread.currentThread().name}") withContext(Dispatchers.IO) { println("withContext thread is:${Thread.currentThread().name}") delay(2000) } //上面的IO線程中執(zhí)行時(shí)稀蟋,會(huì)將子協(xié)程掛起狭园,下面的打印要等待IO線程執(zhí)行完成 println("launch thread3 is:${Thread.currentThread().name}") } println("launch end thread is:${Thread.currentThread().name}") delay(1000) //再次將MainScope().launch生成的協(xié)程掛起,UI線程繼續(xù)執(zhí)行 println("MainScope().launch end thread is:${Thread.currentThread().name}") } }
輸出:
16:02:15.525 : test end 16:02:15.752 : MainScope().launch start thread is:main 16:02:20.794 : launch start thread is:main 16:02:20.796 : launch end thread is:main 16:02:20.799 : launch thread1 is:main 16:02:20.805 : withContext thread is:DefaultDispatcher-worker-1 16:02:21.839 : MainScope().launch end thread is:main 16:02:22.847 : launch thread3 is:main
上面的例子中糊治,創(chuàng)建了兩個(gè)協(xié)程唱矛,
MainScope().launch
創(chuàng)建的是父協(xié)程,launch
在父協(xié)程內(nèi)部創(chuàng)建了一個(gè)子協(xié)程,delay(5000)
是一個(gè)掛起函數(shù)绎谦,它將父協(xié)程整體掛起了5秒管闷,這5秒內(nèi)UI線程不會(huì)被阻塞,但是父協(xié)程內(nèi)部delay(5000)
后面的代碼不會(huì)執(zhí)行窃肠, 5秒后父協(xié)程恢復(fù)執(zhí)行包个;子協(xié)程
withContext(Dispatchers.IO)
會(huì)將子協(xié)程切換到子線程中運(yùn)行,可以看到子協(xié)程也被掛起了冤留,等withContext(Dispatchers.IO)
里面的代碼執(zhí)行完畢后碧囊,才恢復(fù)執(zhí)行父協(xié)程中
delay(1000)
會(huì)再次將父協(xié)程掛起,說(shuō)明協(xié)程能被多次掛起與恢復(fù)有個(gè)問(wèn)題:子協(xié)程被掛起了纤怒,父協(xié)程會(huì)被掛起嗎糯而?答案是不會(huì),從上面的例子可看出泊窘,當(dāng)子協(xié)程執(zhí)行
delay(2000)
被掛起時(shí)熄驼,父協(xié)程打印出了ddd MainScope().launch end thread is:main
,說(shuō)明子協(xié)程被掛起時(shí)烘豹,父協(xié)程會(huì)繼續(xù)運(yùn)行瓜贾。同理父協(xié)程被掛起也不會(huì)導(dǎo)致子協(xié)程被掛起。 影響代碼順序執(zhí)行的因素
協(xié)程的啟動(dòng)模式
協(xié)程的四種啟動(dòng)模式雖說(shuō)除了LAZY
之外携悯,其它都是在創(chuàng)建時(shí)立即調(diào)度執(zhí)行祭芦,但是協(xié)程內(nèi)部代碼的執(zhí)行一般晚于其外部代碼(多線程下可能會(huì)早于,取決于線程的調(diào)度)憔鬼,比如上面的例子中实束,test()
函數(shù)創(chuàng)建了一個(gè)協(xié)程,其內(nèi)部的代碼執(zhí)行是晚于println("ddd test end")
這條的逊彭,協(xié)程內(nèi)部的執(zhí)行要等待線程調(diào)度的到來(lái)咸灿。LAZY
模式就更不用說(shuō)了,需要顯示調(diào)用才會(huì)開(kāi)始執(zhí)行內(nèi)部邏輯
Suspend function
掛起函數(shù)能掛起協(xié)程并恢復(fù)侮叮,所以自然的可以影響程序執(zhí)行順序
await
避矢、join
函數(shù)
使用Defrred.await()
與Job.join()
都可以使協(xié)程等待其它協(xié)程的執(zhí)行結(jié)果,屬于Suspend function
的特例囊榜,好處是可以無(wú)縫銜接程序的并發(fā)執(zhí)行
Defrred.await()
能讓調(diào)用await
的協(xié)程掛起并等待Defrred
對(duì)象所代表的協(xié)程執(zhí)行完畢后立即恢復(fù)執(zhí)行
Job.join()
可以讓子協(xié)程執(zhí)行完畢后父協(xié)程才會(huì)執(zhí)行完畢
也許我們用delay
也能實(shí)現(xiàn)想要的順序审胸,但是卻不能實(shí)現(xiàn)無(wú)縫銜接,舉例:
private suspend fun intValue1(): Int {
delay(1000) //模擬多線程執(zhí)行時(shí)間 1秒
return 1
}
private suspend fun intValue2(): Int {
delay(2000) //模擬多線程執(zhí)行時(shí)間 2秒
return 2
}
fun main() = runBlocking {
val elapsedTime = measureTimeMillis {
var value1 = 0
var value2 = 0
//下面兩個(gè)協(xié)程也是在并發(fā)執(zhí)行
async { value1 = intValue1() }
async { value2 = intValue2() }
delay(3000)
println("the result is ${value1 + value2}")
}
println("the elapsedTime is $elapsedTime")
}
輸出:
the result is 3
the elapsedTime is 3012
上面代碼中卸勺,我們大概知道兩個(gè)協(xié)程執(zhí)行的時(shí)間砂沛,所以等3秒后肯定能得到正確結(jié)果,2.5秒也可以曙求,但是這種方式問(wèn)題很大碍庵,因?yàn)檫@個(gè)等待的時(shí)間不好把握映企,等待時(shí)間過(guò)長(zhǎng)效率不高,等待時(shí)間過(guò)短静浴,有可能子協(xié)程還沒(méi)出結(jié)果堰氓。如果采用Defrred.await()
,就能完美解決苹享,改一下:
private suspend fun intValue1(): Int {
delay(1000) //模擬多線程執(zhí)行時(shí)間 1秒
return 1
}
private suspend fun intValue2(): Int {
delay(2000) //模擬多線程執(zhí)行時(shí)間 2秒
return 2
}
fun main() = runBlocking {
val elapsedTime = measureTimeMillis {
val value1 = async { intValue1() }
val value2 = async { intValue2() }
println("the result is ${value1.await() + value2.await()}")
}
println("the elapsedTime is $elapsedTime")
}
輸出:
the result is 3
the elapsedTime is 2022
Job.join()
的例子:
fun main() = runBlocking {
//注意双絮,GlobalScope.launch生成的協(xié)程并不是runBlocking的子協(xié)程
GlobalScope.launch {
launch {
delay(2000)
println("inner launch1")
}
launch {
delay(1000)
println("inner launch2")
}
}
println("end")
}
輸出:
end
加上delay
fun main() = runBlocking {
//注意,GlobalScope.launch生成的協(xié)程并不是runBlocking的子協(xié)程
GlobalScope.launch {
launch {
delay(2000)
println("inner launch1")
}
launch {
delay(1000)
println("inner launch2")
}
}
delay(3000)
println("end")
}
輸出:
inner launch2
inner launch1
end
上面雖然是我們要的結(jié)果得问,但是這個(gè)delay
的時(shí)間不好把握囤攀,用join
:
fun main() = runBlocking {
val job = GlobalScope.launch {
launch {
delay(2000)
println("inner launch1")
}
launch {
delay(1000)
println("inner launch2")
}
}
job.join()
println("end")
}
輸出:
inner launch2
inner launch1
end
-
代碼執(zhí)行順序規(guī)律
協(xié)程外部,不考慮協(xié)程內(nèi)部宫纬,代碼按常規(guī)順序執(zhí)行
掛起函數(shù)為偽掛起函數(shù)時(shí)焚挠,則相當(dāng)于普通函數(shù),起不到掛起函數(shù)的作用
協(xié)程內(nèi)部哪怔,沒(méi)有子協(xié)程時(shí),無(wú)論是否有掛起函數(shù)向抢,有無(wú)切換線程认境,代碼按常規(guī)順序執(zhí)行
協(xié)程內(nèi)部,有子協(xié)程時(shí)挟鸠,如果父子協(xié)程位于同一線程叉信,則父協(xié)程的掛起函數(shù)掛起時(shí)會(huì)暫停父協(xié)程執(zhí)行,子協(xié)程內(nèi)部代碼開(kāi)始執(zhí)行艘希,父協(xié)程的恢復(fù)執(zhí)行取決于子協(xié)程是否會(huì)掛起或執(zhí)行完硼身;如果父子協(xié)程是多線程并發(fā),執(zhí)行順序符合一般的多線程運(yùn)行規(guī)律覆享,如果父協(xié)程被掛起佳遂,其恢復(fù)取決于父協(xié)程被掛起時(shí)的掛起函數(shù)
舉例:
override fun onCreate(savedInstanceState: Bundle?) {
......
test()
println("test end")
}
fun test() {
MainScope().launch {
val job = GlobalScope.launch(Dispatchers.Main) {
launch {
println("inner launch1 start thread:${Thread.currentThread()}")
var i = 0
while (i++ <= 5){
println("inner launch1 print $i")
Thread.sleep(500)
}
println("inner launch1 end")
}
launch {
println("inner launch2 start thread:${Thread.currentThread()}")
var i = 0
while (i++ <= 5){
println("inner launch2 print $i")
Thread.sleep(500)
}
println("inner launch2 end")
}
println("withContext creating")
withContext(Dispatchers.IO) {
println("withContext thread:${Thread.currentThread().name}")
}
println("out launch end thread:${Thread.currentThread().name}")
}
job.join()
println("fun end")
}
}
輸出:
test end
withContext creating
withContext thread:DefaultDispatcher-worker-1
inner launch1 start thread:Thread[main,5,main]
inner launch1 print 1
inner launch1 print 2
inner launch1 print 3
inner launch1 print 4
inner launch1 print 5
inner launch1 print 6
inner launch1 end
inner launch2 start thread:Thread[main,5,main]
inner launch2 print 1
inner launch2 print 2
inner launch2 print 3
inner launch2 print 4
inner launch2 print 5
inner launch2 print 6
inner launch2 end
out launch end thread:main
fun end
父協(xié)程在執(zhí)行掛起函數(shù)withContext
時(shí),子協(xié)程開(kāi)始運(yùn)行撒顿,如果我們將withContext
也改在Main
線程
withContext(Dispatchers.IO) {
println("withContext thread:${Thread.currentThread().name}")
}
改為
withContext(Dispatchers.Main) {
println("withContext thread:${Thread.currentThread().name}")
}
結(jié)果為:
test end
withContext creating
withContext thread:main
out launch end thread:main
inner launch1 start thread:Thread[main,5,main]
inner launch1 print 1
inner launch1 print 2
inner launch1 print 3
inner launch1 print 4
inner launch1 print 5
inner launch1 print 6
inner launch1 end
inner launch2 start thread:Thread[main,5,main]
inner launch2 print 1
inner launch2 print 2
inner launch2 print 3
inner launch2 print 4
inner launch2 print 5
inner launch2 print 6
inner launch2 end
fun end
從結(jié)果能看到withContext
雖然是掛起函數(shù)丑罪,但是其里面的執(zhí)行線程沒(méi)有變化,并沒(méi)有起到掛起的作用
改為多線程:
fun test() {
MainScope().launch {
val job = GlobalScope.launch {
launch {
println("inner launch1 start thread:${Thread.currentThread()}")
var i = 0
while (i++ <= 5){
println("inner launch1 print $i")
Thread.sleep(500)
}
println("inner launch1 end")
}
launch {
println("inner launch2 start thread:${Thread.currentThread()}")
var i = 0
while (i++ <= 5){
println("inner launch2 print $i")
Thread.sleep(500)
}
println("inner launch2 end")
}
println("withContext creating")
withContext(Dispatchers.Main) {
println("withContext thread:${Thread.currentThread().name}")
}
println("out launch end thread:${Thread.currentThread().name}")
}
job.join()
println("fun end")
}
}
輸出:
test end
inner launch1 start thread:Thread[DefaultDispatcher-worker-1,5,main]
inner launch1 print 1
inner launch2 start thread:Thread[DefaultDispatcher-worker-3,5,main]
inner launch2 print 1
withContext creating
withContext thread:main
out launch end thread:DefaultDispatcher-worker-4
inner launch1 print 2
inner launch2 print 2
inner launch1 print 3
inner launch2 print 3
inner launch1 print 4
inner launch2 print 4
inner launch1 print 5
inner launch2 print 5
inner launch1 print 6
inner launch2 print 6
inner launch1 end
inner launch2 end
fun end
可以看出凤壁,父子協(xié)程運(yùn)行就是一個(gè)多線程并發(fā)的方式吩屹,如果不考慮子協(xié)程,父協(xié)程里的代碼執(zhí)行就是常規(guī)的順序執(zhí)行
四拧抖、協(xié)程核心概念
-
CoroutineScope
協(xié)程作用域(
Coroutine Scope
)是協(xié)程運(yùn)行的作用范圍煤搜,CoroutineScope
定義了新啟動(dòng)的協(xié)程作用范圍,同時(shí)會(huì)繼承了他的coroutineContext
自動(dòng)傳播其所有的elements
和取消操作唧席。換句話說(shuō)擦盾,如果這個(gè)作用域銷毀了嘲驾,那么里面的協(xié)程也隨之失效。前面說(shuō)過(guò)厌衙,全局的
GlobalScope
是一個(gè)作用域距淫,每個(gè)協(xié)程自身也是一個(gè)作用域,新建的協(xié)程與它的父作用域存在一個(gè)級(jí)聯(lián)的關(guān)系驗(yàn)證一下:
fun main() = runBlocking { val job = GlobalScope.launch { //this:CoroutineScope println("GlobalScope is :$GlobalScope") println("GlobalScope.launch's coroutineScope is :$this") launch { //this:CoroutineScope println("launch's coroutineScope is :$this") launch { //this:CoroutineScope println("launch child's coroutineScope is :$this") } } async { //this:CoroutineScope println("async's coroutineScope is :$this") launch { //this:CoroutineScope println("async child's coroutineScope is :$this") } } } job.join() }
輸出:
GlobalScope is :kotlinx.coroutines.GlobalScope@781d90ae GlobalScope.launch's coroutineScope is :StandaloneCoroutine{Active}@74556741 launch's coroutineScope is :StandaloneCoroutine{Active}@1cad377c launch child's coroutineScope is :StandaloneCoroutine{Active}@41b5b14e async's coroutineScope is :DeferredCoroutine{Active}@6a54e1c async child's coroutineScope is :StandaloneCoroutine{Active}@3522e255
上面的例子中婶希,所有的協(xié)程都是在
GlobalScope
之中創(chuàng)建的榕暇,但可以看出,里面生成的所有新協(xié)程的CoroutineScope
對(duì)象都是新創(chuàng)建 的喻杈,全都不同彤枢,為什么會(huì)這樣,看源碼就能明白:public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, //block代表的lambda的調(diào)用對(duì)象是CoroutineScope類型 block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) //block傳到了start函數(shù)之中 coroutine.start(start, coroutine, block) return coroutine } //block的調(diào)用對(duì)象變?yōu)榱薘類型筒饰,而R類型是receiver表示的參數(shù)確定的缴啡,receiver的實(shí)參是launch函數(shù)中的coroutine變量 //其為L(zhǎng)azyStandaloneCoroutine或者StandaloneCoroutine對(duì)象 public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) { startAbstractCoroutine(start, receiver, this, block) }
從源碼得知,
launch
函數(shù)調(diào)用時(shí){}
所包含的代碼塊的調(diào)用主體最終變成了LazyStandaloneCoroutine
或StandaloneCoroutine
對(duì)象(這兩類型的父類型也是CoroutineScope
)瓷们,這個(gè)對(duì)象是在launch
中新創(chuàng)建的业栅。async
函數(shù)也類似。雖然不同谬晕,但是其
CoroutineContext
變量會(huì)進(jìn)行傳遞碘裕,保證了協(xié)程的結(jié)構(gòu)化并發(fā)特征。為什么這樣做攒钳,我的理解是CoroutineScope
只是一個(gè)接口帮孔,只包含一個(gè)變量,太過(guò)于簡(jiǎn)單不撑,為了攜帶更多信息文兢,所以要進(jìn)行轉(zhuǎn)換。 -
Job
一個(gè)
Job
是對(duì)一個(gè)協(xié)程的句柄焕檬。你創(chuàng)建的每個(gè)協(xié)程姆坚,不管你是通過(guò)launch
還是async
來(lái)啟動(dòng)的,它都會(huì)返回一個(gè)Job
實(shí)例实愚,唯一標(biāo)識(shí)該協(xié)程旷偿,并可以通過(guò)該Job
管理其生命周期。在CoroutineScope
的構(gòu)造函數(shù)中也傳入了一個(gè)Job
爆侣,可以保持對(duì)其生命周期的控制萍程。Job
的生命周期由狀態(tài)表示,下面是其狀態(tài)機(jī)圖示:
在 “Active”
狀態(tài)下兔仰,一個(gè) Job
正在運(yùn)行并執(zhí)行它的工作茫负。如果 Job
是通過(guò)協(xié)程構(gòu)建器創(chuàng)建的,這個(gè)狀態(tài)就是協(xié)程主體運(yùn)行時(shí)的狀態(tài)乎赴。在這種狀態(tài)下忍法,我們可以啟動(dòng)子協(xié)程潮尝。大多數(shù)協(xié)程會(huì)在 “Active”
狀態(tài)下啟動(dòng)。只有那些延遲啟動(dòng)的才會(huì)以 “New”
狀態(tài)啟動(dòng)饿序。當(dāng)它完成時(shí)候勉失,它的狀態(tài)變?yōu)?“Completing”
,等待所有子協(xié)程完成原探。一旦它的所有子協(xié)程任務(wù)都完成了乱凿,其狀態(tài)就會(huì)變?yōu)?“Completed”
,這是一個(gè)最終狀態(tài)咽弦⊥襟。或者,如果 Job
在運(yùn)行時(shí)候(在 “Active”
或者 “Completing”
狀態(tài)下)取消或失敗型型,其狀態(tài)將會(huì)改變成為 “Cancelling”
段审。在這種狀態(tài)下,最后還可以做一些清理工作闹蒜,比如關(guān)閉連接或釋放資源寺枉。完成此操作后, Job
將會(huì)進(jìn)入到 “Cancelled”
狀態(tài)绷落。
Job
存在父子關(guān)系姥闪,比如:
val grandfatherJob = SupervisorJob()
//創(chuàng)建一個(gè)Job,
val job = GlobalScope.launch(grandfatherJob) {
//啟動(dòng)一個(gè)子協(xié)程
val childJob = launch {
}
}
上面的代碼中有三個(gè)Job
:grandfatherJob嘱函、job甘畅、childJob
埂蕊,其中job
父親為grandfatherJob
往弓,childJob
父親為job
增加打印語(yǔ)句,來(lái)印證一下:
fun main() = runBlocking{
val grandfatherJob = SupervisorJob()
//創(chuàng)建一個(gè)Job蓄氧,
val job = GlobalScope.launch(grandfatherJob) {
println("job start")
//啟動(dòng)一個(gè)子協(xié)程
val childJob = launch {
println("childJob start")
}
println("job end")
}
println("job's child is ${job.children.elementAtOrNull(0)}")
println("grandfatherJob's child is ${grandfatherJob.children.elementAtOrNull(0)}")
println("end")
}
輸出:
job start
job end
childJob start
job's child is null
grandfatherJob's child is null
end
上面不是說(shuō):job
父親為grandfatherJob
函似,childJob
父親為job
,為什么打印出來(lái)job
與grandfatherJob
的子協(xié)程都為空呢喉童?
主要是如果子協(xié)程如果執(zhí)行完了撇寞,會(huì)自動(dòng)從children
這個(gè)Sequence
中清除掉,如果我們?cè)诖蛴?code>child時(shí)堂氯,讓子協(xié)程還在運(yùn)行中:
fun main() = runBlocking{
val grandfatherJob = SupervisorJob()
//創(chuàng)建一個(gè)Job蔑担,
val job = GlobalScope.launch(grandfatherJob) {
println("job start")
//啟動(dòng)一個(gè)子協(xié)程
val childJob = launch {
println("childJob start")
delay(1000) //延遲1秒
}
delay(2000) //延遲2秒
println("job end")
}
println("job's child is ${job.children.elementAtOrNull(0)}")
println("grandfatherJob's child is ${grandfatherJob.children.elementAtOrNull(0)}")
println("end")
}
結(jié)果如下:
job start
childJob start
job's child is StandaloneCoroutine{Active}@59e5ddf
grandfatherJob's child is StandaloneCoroutine{Active}@536aaa8d
end
運(yùn)行結(jié)果與預(yù)想一致。
Job
的父子關(guān)系如何建立:
協(xié)程構(gòu)建器基于其父 Job
構(gòu)建其 Job
每個(gè)協(xié)程構(gòu)建器都會(huì)創(chuàng)建其它們自己的 Job
咽白,大多數(shù)協(xié)程構(gòu)建器會(huì)返回 Job
Job
是唯一一個(gè)不是子協(xié)程直接繼承父協(xié)程的上下文(上下文即CoroutineContext
啤握,Job
也是繼承自CoroutineContext
)。每個(gè)協(xié)程都會(huì)創(chuàng)建自己的 Job
晶框,來(lái)自傳遞參數(shù)或者父協(xié)程的 Job
將會(huì)被用作這個(gè)子協(xié)程所創(chuàng)建 Job
的父 Job
排抬,比如:
fun main() = runBlocking {
val name = CoroutineName("Some name")
val job = Job()
launch(name + job) {
val childName = coroutineContext[CoroutineName]
println(childName == name) // true
//childJob是在launch中新建的Job,但其與”val job = Job()“中的job保持著父子關(guān)系
val childJob = coroutineContext[Job]
println(childJob == job) // false
println(childJob == job.children.first()) // true
}
}
如果新的 Job
上下文取代了父 Job
的上下文懂从,結(jié)構(gòu)化并發(fā)機(jī)制將不起作用,比如:
fun main(): Unit = runBlocking {
launch(Job()) { // 使用新 Job 取代了來(lái)自父協(xié)程的 Job
delay(1000)
println("Will not be printed")
}
}
// (不會(huì)打印任何東西蹲蒲,程序會(huì)馬上結(jié)束))
在上面的例子中番甩,父協(xié)程將不會(huì)等待子協(xié)程,因?yàn)樗c子協(xié)程沒(méi)有建立關(guān)系届搁,因?yàn)樽訁f(xié)程使用來(lái)自參數(shù)的 Job
作為父 Job
缘薛,因此它與 runBlocking
的Job
沒(méi)有關(guān)系。
下面再用兩段程序作說(shuō)明:
private fun test1() {
//總共有5個(gè)Job:SupervisorJob咖祭、newJob掩宜、Job0、Job1么翰、Job2
val scope = MainScope() //SupervisorJob(無(wú)子Job)
//Job()會(huì)生成newJob牺汤,scope.launch會(huì)生成Job0,而Job0的父Job是newJob,Job0的子Job是Job1浩嫌、Job2
scope.launch(Job()) { //此處使用新 Job 取代了來(lái)自父協(xié)程的 Job
launch { //Job1
delay(2000L)
println("CancelJobActivity job1 finished")
scope.cancel()
}
launch { //Job2
delay(3000L)
println("CancelJobActivity job2 finished") //會(huì)輸出
}
}
}
private fun test2() {
//總共有4個(gè)Job:SupervisorJob檐迟、Job0、Job1码耐、Job2
val scope = MainScope()//SupervisorJob(子Job為Job0)
scope.launch { //Job0(子Job為Job1追迟、Job2)
launch { //Job1
delay(2000L)
println("CancelJobActivity job1 finished")
scope.cancel()
}
launch { //Job2
delay(3000L)
println("CancelJobActivity job2 finished") //不會(huì)輸出
}
}
}
Job
使用join
方法用來(lái)等待,直到所有協(xié)程完成骚腥。這是一個(gè)掛起函數(shù)敦间,它掛起直到每個(gè)具體的子 Job
達(dá)到最終狀態(tài)(Completed
或者 Cancelled
)。比如:
fun main(): Unit = runBlocking {
val job1 = launch {
delay(1000)
println("Test1")
}
val job2 = launch {
delay(2000)
println("Test2")
}
job1.join()
job2.join()
println("All tests are done")
}
輸出:
Test1
Test2
All tests are done
上面例子中束铭,可以看到Job
接口還暴露了一個(gè) children
屬性廓块,允許我們?cè)L問(wèn)它的所有子 job
,比如:
fun main(): Unit = runBlocking {
launch {
delay(1000)
println("Test1")
}
launch {
delay(2000)
println("Test2")
}
val children = coroutineContext[Job]
?.children
val childrenNum = children?.count()
println("Number of children: $childrenNum")
children?.forEach { it.join() }
println("All tests are done")
}
輸出:
Number of children: 2
Test1
Test2
All tests are done
理解:join()
調(diào)用在哪個(gè)協(xié)程之中契沫,則這個(gè)協(xié)程的結(jié)束需要等待調(diào)用join
函數(shù)的Job
結(jié)束带猴。上面的例子,join()
在runBlocking
之中被調(diào)用懈万,所以runBlocking
要結(jié)束拴清,需要等待job1、job2
先結(jié)束会通。
-
CoroutineContext
CoroutineContext
管理了協(xié)程的生命周期口予,線程調(diào)度,異常處理等功能涕侈,在創(chuàng)建協(xié)程時(shí)沪停,都會(huì)新建一個(gè)CoroutineContext
對(duì)象,該對(duì)象可以手動(dòng)創(chuàng)建傳入或者會(huì)自動(dòng)創(chuàng)建一個(gè)默認(rèn)值CoroutineContext
是一個(gè)特殊的集合驾凶,既有Map
的特點(diǎn)牙甫,也有Set
的特點(diǎn)掷酗,集合的每一個(gè)元素都是Element
,每個(gè)Element
都有一個(gè)Key
與之對(duì)應(yīng)窟哺,對(duì)于相同Key
的Element
是不可以重復(fù)存在的泻轰,Element
之間可以通過(guò) + 號(hào)組合起來(lái)CoroutineContext包含了如下
Element
元素:Job:協(xié)程的唯一標(biāo)識(shí),用來(lái)控制協(xié)程的生命周期
(new且轨、active浮声、completing、completed旋奢、cancelling泳挥、cancelled)
,默認(rèn)為null
至朗,比如GlobalScope
中的Job
為null
;CoroutineDispatcher:指定協(xié)程運(yùn)行的線程
(IO屉符、Default、Main锹引、Unconfined)
矗钟,默認(rèn)為Default;
CoroutineName:協(xié)程的名稱,調(diào)試的時(shí)候很有用嫌变,默認(rèn)為
coroutine;
CoroutineExceptionHandler:指定協(xié)程的異常處理器吨艇,用來(lái)處理未捕獲的異常.
與
Element
元素對(duì)應(yīng)的Key
,可以直接用Element
元素本身腾啥,比如要從CoroutineContext
中獲取Job
元素的值东涡,通過(guò)CoroutineContext[Job]
即可,之所以能這能這樣倘待,在于Kotlin
有一個(gè)特性:一個(gè)類的名字本身就可以作為其伴生對(duì)象的引用疮跑,所以coroutineContext[Job]
只是coroutineContext[Job.Key]
的一個(gè)簡(jiǎn)寫方式,實(shí)際上最原始的寫法應(yīng)該是這樣:coroutineContext[object : CoroutineContext.Key<Job> {}]
延柠,Job
的源碼部分片斷:public interface Job : CoroutineContext.Element { public companion object Key : CoroutineContext.Key<Job> ...... }
上面的伴生對(duì)象
Key
祸挪,可以直接用Job
來(lái)替代锣披。實(shí)際上
Job贞间、CoroutineDispatcher、CoroutineName雹仿、CoroutineExceptionHandler
都是CoroutineContext
類型增热,因?yàn)樗鼈兌祭^承自CoroutineContext.Element
,而CoroutineContext.Element繼承自CoroutineContext
胧辽。既然Element
是元素峻仇,CoroutineContext
是集合,為什么元素本身也是集合呢邑商,為什么要這樣設(shè)計(jì)摄咆?主要是為了API
設(shè)計(jì)方便凡蚜,能非常方便的實(shí)現(xiàn) +操作。對(duì)于新創(chuàng)建的協(xié)程吭从,它的
CoroutineContext
會(huì)包含一個(gè)全新的Job
實(shí)例朝蜘,它會(huì)幫助我們控制協(xié)程的生命周期,而剩下的元素會(huì)從CoroutineContext
的父類繼承涩金,該父類可能是另外一個(gè)協(xié)程或者創(chuàng)建該協(xié)程的CoroutineScope
谱醇。fun main() { val b = runBlocking { println("Level 0 Job:${coroutineContext[Job]}") val scope = CoroutineScope(Job()+Dispatchers.IO+CoroutineName("test")) println("Level 1 Job:${scope.coroutineContext[Job]}") val job= scope.launch { //新的協(xié)程會(huì)將CoroutineScope作為父級(jí) println("Level 2 Job:${coroutineContext[Job]}") println("Level 2 CoroutineName:${coroutineContext[CoroutineName]}") println("Level 2 CoroutineDispatcher:${coroutineContext[CoroutineDispatcher]}") println("Level 2 CoroutineExceptionHandler:${coroutineContext[CoroutineExceptionHandler]}") println("Level 2 Thread:${Thread.currentThread().name}") val result = async { //通過(guò)async創(chuàng)建的新協(xié)程會(huì)將當(dāng)前協(xié)程作為父級(jí) println("Level 3 Job:${coroutineContext[Job]}") println("Level 3 CoroutineName:${coroutineContext[CoroutineName]}") println("Level 3 CoroutineDispatcher:${coroutineContext[CoroutineDispatcher]}") println("Level 3 Thread:${Thread.currentThread().name}") }.await() } job.join() } }
輸出:
Level 0 Job:BlockingCoroutine{Active}@5f9d02cb Level 1 Job:JobImpl{Active}@3a5ed7a6 Level 2 Job:StandaloneCoroutine{Active}@2f3b4d1b Level 2 CoroutineName:CoroutineName(test) Level 2 CoroutineDispatcher:Dispatchers.IO Level 2 CoroutineExceptionHandler:null Level 2 Thread:DefaultDispatcher-worker-1 Level 3 Job:DeferredCoroutine{Active}@74a4da96 Level 3 CoroutineName:CoroutineName(test) Level 3 CoroutineDispatcher:Dispatchers.IO Level 3 Thread:DefaultDispatcher-worker-3
CoroutineContext
生成規(guī)則:新創(chuàng)建的
CoroutineContext
= 默認(rèn)值 + 繼承的CoroutineContext
+參數(shù)元素不指定的話,會(huì)給出默認(rèn)值步做,比如:
CoroutineDispatcher
默認(rèn)值為Dispatchers.Default
副渴,CoroutineName
默認(rèn)值為"coroutine"
繼承的
CoroutineContext
是CoroutineScope
或者其父協(xié)程的CoroutineContext
傳入?yún)f(xié)程構(gòu)建器的參數(shù)的優(yōu)先級(jí)高于繼承的上下文參數(shù),因此會(huì)覆蓋對(duì)應(yīng)的參數(shù)值
子協(xié)程繼承父協(xié)程時(shí)全度,除了
Job
會(huì)自動(dòng)創(chuàng)建新的實(shí)例外煮剧,其他3項(xiàng)的不手動(dòng)指定的話,都會(huì)自動(dòng)繼承父協(xié)程的
五将鸵、協(xié)程原理
一個(gè)問(wèn)題:能不能實(shí)現(xiàn)一個(gè)java
庫(kù)轿秧,實(shí)現(xiàn)類似kotlin
協(xié)程的功能?
我認(rèn)為理論上是可以的咨堤,但肯定沒(méi)協(xié)程這般優(yōu)雅菇篡。因?yàn)樾枰猛降姆绞綄懗霎惒綀?zhí)行的代碼,所以代碼肯定需要在編譯前進(jìn)行處理一喘,可以參考一下Butterknife
中用到的APT
(注解處理器)驱还,APT
能根據(jù)注解自動(dòng)生成代碼。掌握了協(xié)程的原理凸克,能更好的回答這個(gè)問(wèn)題议蟆。
-
狀態(tài)機(jī)是什么
表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型,簡(jiǎn)單點(diǎn)說(shuō)就是用幾個(gè)狀態(tài)來(lái)代表和控制執(zhí)行流程萎战。一般同一時(shí)間點(diǎn)只有一種狀態(tài)咐容,每種狀態(tài)對(duì)應(yīng)一種處理邏輯,處理后轉(zhuǎn)為下一個(gè)狀態(tài)蚂维,比如可以用
Switch(kotlin里用when語(yǔ)句)
來(lái)實(shí)現(xiàn)狀態(tài)機(jī):class StateMachine { var state = 0 } fun main() { val stateMachine = StateMachine() repeat(3) { when (stateMachine.state){ 0 -> { println("狀態(tài)0做事") stateMachine.state = 1 } 1 -> { println("狀態(tài)1做事") stateMachine.state = 33 } else -> { println("其他狀態(tài)做事") } } } }
輸出:
狀態(tài)0做事 狀態(tài)1做事 其他狀態(tài)做事
-
回調(diào)函數(shù)+狀態(tài)機(jī)
比如實(shí)現(xiàn)一個(gè)簡(jiǎn)單的回調(diào)函數(shù)+狀態(tài)機(jī):
interface Callback { fun callback() } fun myFunction(Callback: Callback?) { class MyFunctionStateMachine : Callback { var state = 0 override fun callback() { myFunction(this)//調(diào)用本函數(shù) } } val machine = if (Callback == null) MyFunctionStateMachine() else Callback as MyFunctionStateMachine when (machine.state) { 0 -> { println("狀態(tài)0做事") machine.state = 1 machine.callback() } 1 -> { println("狀態(tài)1做事") machine.state = 33 machine.callback() } else -> { println("其他狀態(tài)做事") machine.state = 0 machine.callback() } } } fun main() { myFunction(null) }
輸出:
狀態(tài)0做事 狀態(tài)1做事 其他狀態(tài)做事 狀態(tài)0做事 狀態(tài)1做事 其他狀態(tài)做事 ......
會(huì)一直循環(huán)輸出:
狀態(tài)0做事
狀態(tài)1做事
其他狀態(tài)做事
-
suspend 函數(shù)的真面目
編譯器會(huì)將
suspend
函數(shù)變成一個(gè)"回調(diào)函數(shù)+狀態(tài)機(jī)"的模式戳粒,函數(shù)定義的轉(zhuǎn)變,比如:suspend fun getUserInfo(): String { ...... } 變?yōu)椋? public static final Object getUserInfo(@NotNull Continuation var0) { ...... }
suspend fun getFriendList(user: String): String { ...... } 變?yōu)椋? public static final Object getFriendList(@NotNull String var0, @NotNull Continuation var1) { ...... }
可以看到
suspend
函數(shù)參數(shù)中增加了一個(gè)類型為Continuation
的參數(shù)虫啥,Kotlin Compiler
使用Continuation
參數(shù)代替了suspend
修飾符蔚约,看看Continuation
的定義:public interface Continuation<in T> { public val context: CoroutineContext public fun resumeWith(result: Result<T>) }
Continuation
類型變量就是充當(dāng)了回調(diào)函數(shù)的角色,這個(gè)從掛起函數(shù)轉(zhuǎn)換成CallBack函數(shù)
的過(guò)程涂籽,被稱為:CPS 轉(zhuǎn)換(Continuation-Passing-Style Transformation)
苹祟,即續(xù)體傳遞風(fēng)格變換,我們可以稱Continuation
為一個(gè)續(xù)體函數(shù)的返回類型也會(huì)變?yōu)?code>Any?(表現(xiàn)在
Java
字節(jié)碼中則為Object
),這是因?yàn)槿绻?code>suspend函數(shù)里面調(diào)用了delay
之類的函數(shù)導(dǎo)致suspended
發(fā)生的話树枫,函數(shù)會(huì)返回一個(gè)enum
類型:COROUTINE_SUSPENDED
直焙,所以需要用Any?
作返回類型
看看函數(shù)體的轉(zhuǎn)變:
suspend fun getUserInfo(): String {
delay(5000L)
return "BoyCoder"
}
變?yōu)椋?
public static final Object getUserInfo(@NotNull Continuation var0) {
Object $continuation;
label20: {
if (var0 instanceof GetUserInfoMachine) {
$continuation = (GetUserInfoMachine)var0;
if (($continuation).label & Integer.MIN_VALUE) != 0) {
$continuation).label -= Integer.MIN_VALUE;
break label20;
}
}
$continuation = new GetUserInfoMachine(var0);
}
Object $result = $continuation).result;
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch($continuation).label) {
case 0:
ResultKt.throwOnFailure($result);
$continuation.label = 1;
if (DelayKt.delay(5000L, (Continuation)$continuation) == var3) {
return var3;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return "BoyCoder";
}
static final class GetUserInfoMachine extends ContinuationImpl {
Object result;
int label;
GetUserInfoMachine(Continuation $completion) {
super($completion);
}
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return TestKt.getUserInfo(null, (Continuation<? super String>) this);
}
}
GetUserInfoMachine
的繼承關(guān)系如下:
GetUserInfoMachine -> ContinuationImpl -> BaseContinuationImpl -> Continuation
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
// This implementation is final. This fact is used to unroll resumeWith recursion.
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) {
// 在每個(gè)恢復(fù)的continuation進(jìn)行調(diào)試探測(cè),使得調(diào)試庫(kù)可以精確跟蹤掛起的調(diào)用棧中哪些部分
// 已經(jīng)恢復(fù)了砂轻。
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
completion.resumeWith(outcome)
return
}
}
}
}
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
protected open fun releaseIntercepted() {
// does nothing here, overridden in ContinuationImpl
}
...
}
delay的定義:
public static final Object delay(long timeMillis, @NotNull Continuation $completion) {
if (timeMillis <= 0L) {
return Unit.INSTANCE;
} else {
// 實(shí)現(xiàn)類
CancellableContinuationImpl cancellableContinuationImpl = new CancellableContinuationImpl(IntrinsicsKt.intercepted($completion), 1);
cancellableContinuationImpl.initCancellability();
// 向上轉(zhuǎn)型
CancellableContinuation cont = (CancellableContinuation)cancellableContinuationImpl;
if (timeMillis < Long.MAX_VALUE) {
// 延時(shí)操作
getDelay(cont.getContext()).scheduleResumeAfterDelay(timeMillis, cont);
}
// 獲取執(zhí)行結(jié)果
Object result = cancellableContinuationImpl.getResult();
if (result == COROUTINE_SUSPENDED) {
DebugProbesKt.probeCoroutineSuspended($completion);
}
// 返回結(jié)果
return result;
}
}
getUserInfo
函數(shù)執(zhí)行的過(guò)程:
1.調(diào)用getUserInfo
函數(shù)箕般,傳入var0
(即自動(dòng)產(chǎn)生的續(xù)體對(duì)象,Continuation
類型)舔清,其不為GetUserInfoMachine
類型丝里,所以new
了一個(gè)GetUserInfoMachine
對(duì)象,并將var0
保存到GetUserInfoMachine
對(duì)象中体谒,同時(shí)將GetUserInfoMachine
對(duì)象賦給$continuation
變量
2.由于$continuation.label = 0
杯聚,執(zhí)行case 0
分支
3.case 0
分支中將$continuation.label
置為1
,調(diào)用DelayKt.delay
方法
4.執(zhí)行delay
方法抒痒,$continuation
傳入到delay
中(保存在變量$completion
中幌绍,協(xié)程恢復(fù)時(shí)會(huì)用到),delay
返回COROUTINE_SUSPENDED
故响,表示掛起
5.case 0
中傀广,直接return
結(jié)果 ,最后getUserInfo
函數(shù)返回COROUTINE_SUSPENDED
6.因?yàn)?code>getUserInfo函數(shù)已返回COROUTINE_SUSPENDED
彩届,getUserInfo
函數(shù)暫時(shí)執(zhí)行完畢伪冰,線程執(zhí)行其它動(dòng)作(通過(guò)暫時(shí)結(jié)束方法調(diào)用的方式,讓協(xié)程暫時(shí)不在這個(gè)線程上面執(zhí)行樟蠕,線程可以去處理其它的任務(wù)贮聂,協(xié)程的掛起就不會(huì)阻塞當(dāng)前的線程,這就是為什么協(xié)程能非阻塞式掛起)
7.目前getUserInfo
函數(shù)所在的協(xié)程處于掛起狀態(tài)寨辩,而delay
函數(shù)會(huì)在某個(gè)子線程執(zhí)行等待操作(這也是為什么我們的suspend
函數(shù)一定要調(diào)用系統(tǒng)的suspend
函數(shù)的原因吓懈,系統(tǒng)的函數(shù)才有這個(gè)能力),等延時(shí)時(shí)間到達(dá)之后靡狞,就會(huì)調(diào)用傳給delay
函數(shù)的$completion
的resumeWith
方法耻警,也就是調(diào)用GetUserInfoMachine
的resumeWith
方法,即BaseContinuationImpl
的resumeWith
方法甸怕,來(lái)進(jìn)行協(xié)程的恢復(fù)
8.BaseContinuationImpl
的resumeWith
方法會(huì)調(diào)用到GetUserInfoMachine
對(duì)象的invokeSuspend
方法
9.invokeSuspend
方法中甘穿,又開(kāi)始調(diào)用getUserInfo
函數(shù),傳入var0
參數(shù)蕾各,此時(shí)var0
為之前創(chuàng)建的GetUserInfoMachine
對(duì)象
10.由于$continuation.label = 1
扒磁,執(zhí)行case 1
分支
11.最后getUserInfo
函數(shù)執(zhí)行結(jié)束并返回了"BoyCoder"
12.此時(shí)回到第8步的BaseContinuationImpl
的resumeWith
方法中庆揪,invokeSuspend
執(zhí)行的結(jié)果即是第11步返回的"BoyCoder"
式曲,保存到了outcome
變量中
13.resumeWith
方法接著執(zhí)行Result.success(outcome)
,并將結(jié)果保存到外部的val outcome: Result<Any?>
,這個(gè)變量中
14.completion
變量(此變量就是第1步中傳入的var0
)此時(shí)不為BaseContinuationImpl
吝羞,最后會(huì)執(zhí)行completion.resumeWith(outcome)
兰伤,表示結(jié)束,我們可以看看源碼里的注釋:// top-level completion reached -- invoke and return
翻譯過(guò)來(lái)就是:達(dá)到頂級(jí)完成--調(diào)用并返回
說(shuō)明一下钧排,如果getUserInfo
函數(shù)調(diào)用的不是delay
敦腔,而是另一個(gè)有返回值的suspend
函數(shù),就會(huì)執(zhí)行if (completion is BaseContinuationImpl) {}
里的代碼恨溜,形成一種遞歸調(diào)用
另外我們說(shuō)過(guò)掛起函數(shù)掛起的是整個(gè)協(xié)程符衔,不是掛起函數(shù)本身,所以第6步里getUserInfo
函數(shù)返回COROUTINE_SUSPENDED
后糟袁,協(xié)程里面判族,getUserInfo
掛起函數(shù)后面的代碼暫時(shí)是不會(huì)執(zhí)行的,協(xié)程本身也是一個(gè)狀態(tài)機(jī)项戴,整個(gè)協(xié)程也暫時(shí)會(huì)返回COROUTINE_SUSPENDED
形帮,所以協(xié)程才會(huì)因?yàn)閽炱鸷瘮?shù)而掛起
-
協(xié)程的真面目
上面只是舉了一個(gè)最簡(jiǎn)單的例子,能作為分析協(xié)程原理的一個(gè)基礎(chǔ)周叮。