一.保持對協(xié)程的追蹤
我們知道協(xié)程可以處理耗時任務,但是假如我們的項目中開啟了一千個協(xié)程處理任務,我們都是通過手動去追蹤它們,那代碼很容易出錯。也很容易失去對協(xié)程的實時跟蹤逃默。就會引起work leak吟税,任務泄漏异旧,類似內(nèi)存泄漏一樣潮针,任務泄漏會導致CPU焦读,內(nèi)存喧兄,磁盤資源被占用恭理。甚至會發(fā)起一些無用的網(wǎng)絡請求。為了避免任務的泄漏及志,Kotlin引入了結(jié)構(gòu)化并發(fā)機制冶共。
在Android平臺使用結(jié)構(gòu)化并發(fā)可以做到以下三件事:
(1)取消任務——當任務不需要繼續(xù)執(zhí)行的時候篡九,取消他
(2)追蹤任務——當任務正在執(zhí)行的時候追蹤他
(3)發(fā)出錯誤的信號——當協(xié)程執(zhí)行失敗時沛善,發(fā)出錯誤信號表明有錯誤發(fā)生
二.借助scope 來取消任務
scope在協(xié)程中稱為作用域媳友,協(xié)程的運行必須指定作用域赶促,CoroutineScope(作用域)可以對協(xié)程進行追蹤空幻,即使協(xié)程被掛起也是如此但两。CoroutineScope并不運行協(xié)程坊罢,他只是確保實時跟著協(xié)程的運行乖仇。因此在Kotlin中不允許在沒有作用域的情況下啟動新的協(xié)程。作用域可以跟蹤所有的協(xié)程,有可以取消由它啟動的協(xié)程艺骂。在Android開發(fā)中經(jīng)常用到。比如在一個Activity中啟動一個協(xié)程巩检,當Activity關(guān)閉的時候劳跃,取消協(xié)程的執(zhí)行谎仲。這個時候我們可以聯(lián)想到讓我們Activity的生命周期保持和作用域的生命周期一樣。以上總結(jié)就是:作用域(CoroutineScope )會跟蹤所有的協(xié)程刨仑,并可以取消由它啟動的協(xié)程郑诺。
三.協(xié)程的啟動方式
啟動協(xié)程,必須要在作用域內(nèi)杉武,而啟動協(xié)程的方式有兩種:
(1)launch構(gòu)建器——啟動新的協(xié)程而不將結(jié)果返回給調(diào)用方
(2)async構(gòu)建器——啟動新的協(xié)程辙诞,通過await的掛起函數(shù)返回result
如:以下launch啟動的協(xié)程,我們只是在掛起函數(shù)doSomthingOne中拿到了返回值轻抱。
package com.example.kotlin01
import kotlinx.coroutines.*
suspend fun main() {
runBlocking {
var job = launch {
var result1 = doSomthingOne()
println(result1)
}
}
}
suspend fun doSomthingOne(): Int {
delay(2000)
return 13
}
13
如:以下async啟動的協(xié)程飞涂,我們通過返回調(diào)用await拿到返回值。
package com.example.kotlin01
import kotlinx.coroutines.*
suspend fun main() {
runBlocking {
var job = async {
doSomthingOne()
}
println(job.await())
}
}
suspend fun doSomthingOne(): Int {
delay(2000)
return 13
}
13
四.在 ViewModel 中啟動協(xié)程
項目中經(jīng)常我們通過使用jetpack中的ViewModel組件來作為View的控制器和Activity綁定起來,這樣以來我們知道即使當我們的手機屏幕旋轉(zhuǎn)Activity異常銷毀重建的時候我們的Viewmodel也不會銷毀较店。如果我們在ViewModel中啟動協(xié)程士八,我們的協(xié)程也不會因此而被終止。我們知道協(xié)程都是運行在作用域中的梁呈,所以就產(chǎn)生了我們的ViewModel.viewModelScope婚度。當viewModelScope被清除,調(diào)用onCleared的時候官卡,我們的協(xié)程也就取消執(zhí)行了蝗茁。比如當一個接口正在請求數(shù)據(jù)的時候,此時用戶關(guān)閉了應用寻咒,那么我們的請求就應該被取消掉哮翘。當一個協(xié)程中又啟動了一個新的協(xié)程,那他們都公用父協(xié)程的作用域仔涩。viewModelScope的使用實例
package com.example.kotlin01
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
fun runForever() {
// 在 ViewModel 中啟動新的協(xié)程
viewModelScope.launch {
// 當 ViewModel 被清除后忍坷,下列代碼也會被取消
while (true){
println("你好")
}
}
}
}
我們看到我們在協(xié)程體中寫了一個死循環(huán),但是假如我們的Viewmodel被清除的時候熔脂,協(xié)程也會被取消佩研。死循環(huán)中的代碼也會取消執(zhí)行。
五.任務追蹤
有時候我們會遇到一些更復雜的問題霞揉,比如在一個協(xié)程中處理多個網(wǎng)絡請求旬薯,我們可以作在一個協(xié)程中又啟動多個協(xié)程去執(zhí)行任務,但是如果隨意啟動協(xié)程适秩,就會容易引起任務泄漏绊序,調(diào)用方可能感知不到啟動了新的協(xié)程,也就無法追蹤秽荞。為了解決這個問題骤公,結(jié)構(gòu)化并發(fā)保證了supend函數(shù)返回時,處理任務也都完成扬跋。我們使用coroutineScope或者supervisorScope構(gòu)造器在supend函數(shù)中啟動新的協(xié)程阶捆,確保了supend函數(shù)返回的時候,他們所做的任務也都能結(jié)束钦听。
suspend fun fetchTwoDocs() {
coroutineScope {
launch { fetchDoc(1) }
async { fetchDoc(2) }
}
}
以上suspend函數(shù)fetchTwoDocs中使用了coroutineScope構(gòu)造器啟動了兩個協(xié)程洒试,當fetchTwoDocs函數(shù)返回的時候,確保了launch和async啟動的協(xié)程也都已經(jīng)完成執(zhí)行朴上。
六.任務追蹤(處理一堆任務)
在以上的例子中垒棋,假如我們在supend函數(shù)中啟動了一千個協(xié)程,那么是否還能正常追蹤呢痪宰,結(jié)果也是一樣的叼架。coroutineScope構(gòu)造器啟動的協(xié)程會等待協(xié)程中的任務執(zhí)行完成畔裕,supend函數(shù)才會返回。并且coroutineScope還會創(chuàng)建一個子scope(作用域)碉碉,我們知道supend函數(shù)肯定是由某個協(xié)程內(nèi)部調(diào)用的柴钻,運行在某個作用域內(nèi),那么suspend函數(shù)中通過coroutineScope啟動的協(xié)程會創(chuàng)建一個子作用域scope 垢粮,這個子作用域繼承了suspend函數(shù)所在協(xié)程的作用域(父作用域)贴届。假如這個父作用域是一個viewScope,當用戶離開界面蜡吧,關(guān)閉Activity的時候毫蚓,viewScope被清除,那么所有的子作用域也會被清楚昔善。子作用域內(nèi)的協(xié)程也會被取消執(zhí)行元潘。這樣就不會造成了任務泄漏。
七.協(xié)程失敗時發(fā)出報錯信號
val unrelatedScope = MainScope()
// 丟失錯誤的例子
suspend fun lostError() {
// 未使用結(jié)構(gòu)化并發(fā)的 async
unrelatedScope.async {
throw InAsyncNoOneCanHearYou("except")
}
}
以上我們在supend函數(shù)中聲明了一個無關(guān)聯(lián)的作用域君仆,它不會按照結(jié)構(gòu)化并發(fā)的方式啟動新的協(xié)程翩概,這段代碼的錯誤將丟失,因為異常通過去獲取返咱,而我們并沒有調(diào)用await 钥庇。所以這個錯誤將永遠不會得到處理。
使用結(jié)構(gòu)化并發(fā)
suspend fun foundError() {
coroutineScope {
async {
throw StructuredConcurrencyWill("throw")
}
}
}
使用結(jié)構(gòu)化并發(fā)就是在supend函數(shù)中使用coroutineScope構(gòu)造器啟動協(xié)程咖摹,coroutineScope將會創(chuàng)建子作用域繼承了supend函數(shù)所在協(xié)程的作用域评姨。如果coroutineScope創(chuàng)建的協(xié)程拋出了異常,coroutineScope將會拋給調(diào)用方萤晴,因為我們使用的是coroutineScope而不是supervisorScope吐句,當拋出異常的時候,所有的子任務就會被取消店读。
八.理解結(jié)構(gòu)化并發(fā)
package com.example.kotlin01
import kotlinx.coroutines.*
suspend fun main() {
println("${doSum()}")
}
suspend fun doSum(): Int = coroutineScope {
var result1 = async { doSomthingOne() }
var result2 = async { doSomthingTwo() }
result1.await() + result2.await()
}
suspend fun doSomthingOne(): Int {
delay(2000)
println("doSomthingOne")
return 13
}
suspend fun doSomthingTwo(): Int {
delay(1000)
println("doSomthingTwo")
return 15
}
在supend 函數(shù)中我們通過coroutineScope 構(gòu)造器啟動了兩個協(xié)程調(diào)用doSomthingOne嗦枢,doSomthingTwo,因為coroutineScope 啟動的協(xié)程的作用域都繼承了supend函數(shù)所在協(xié)程的作用域屯断,當doSomthingOne函數(shù)出現(xiàn)異常的時候文虏,會拋給父作用域,父作用域下協(xié)程的的子協(xié)程都會被取消裹纳。也就是doSomthingOne和doSomthingTwo都會被取消。