今天我們來聊聊Kotlin
的協(xié)程Coroutine
。
如果你還沒有接觸過協(xié)程矮固,推薦你先閱讀這篇入門級文章What? 你還不知道Kotlin Coroutine?
如果你已經(jīng)接觸過協(xié)程棕硫,但對協(xié)程的原理存在疑惑搓萧,那么在閱讀本篇文章之前推薦你先閱讀下面的文章,這樣能讓你更全面更順暢的理解這篇文章奋单。
Kotlin協(xié)程實(shí)現(xiàn)原理:Suspend&CoroutineContext
如果你已經(jīng)接觸過協(xié)程锉试,相信你都有過以下幾個疑問:
- 協(xié)程到底是個什么東西?
- 協(xié)程的
suspend
有什么作用辱匿,工作原理是怎樣的键痛? - 協(xié)程中的一些關(guān)鍵名稱(例如:
Job
炫彩、Coroutine
、Dispatcher
絮短、CoroutineContext
與CoroutineScope
)它們之間到底是怎么樣的關(guān)系江兢? - 協(xié)程的所謂非阻塞式掛起與恢復(fù)又是什么?
- 協(xié)程的內(nèi)部實(shí)現(xiàn)原理是怎么樣的丁频?
- ...
接下來的一些文章試著來分析一下這些疑問杉允,也歡迎大家一起加入來討論。
CoroutineScope
CoroutineScope
是什么席里?如果你覺得陌生叔磷,那么GlobalScope
、lifecycleScope
與viewModelScope
相信就很熟悉了吧(當(dāng)然這個是針對于Android
開發(fā)者)奖磁。它們都實(shí)現(xiàn)了CoroutineScope
接口改基。
public interface CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
*
* By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
*/
public val coroutineContext: CoroutineContext
}
CoroutineScope
中只包含一個待實(shí)現(xiàn)的變量CoroutineContext
,至于CoroutineContext
之前的文章已經(jīng)分析了它的內(nèi)部結(jié)構(gòu)咖为,這里就不再累贅了秕狰。
通過它的結(jié)構(gòu),我們可以認(rèn)為它是提供CoroutineContext
的容器躁染,保證CoroutineContext
能在整個協(xié)程運(yùn)行中傳遞下去鸣哀,約束CoroutineContext
的作用邊界。
例如吞彤,在Android
中使用協(xié)程來請求數(shù)據(jù)我衬,當(dāng)接口還沒有請求完成時Activity
就已經(jīng)退出了,這時如果不停止正在運(yùn)行的協(xié)程將會造成不可預(yù)期的后果饰恕。所以在Activity
中我們都推薦使用lifecycleScope
來啟動協(xié)程挠羔,lifecycleScope
可以讓協(xié)程具有與Activity
一樣的生命周期意識。
下面是lifecycleScope
源碼:
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
它創(chuàng)建了一個LifecycleCoroutineScopeImpl
實(shí)例懂盐,它實(shí)現(xiàn)了CoroutineScope
接口褥赊,同時傳入SupervisorJob() + Dispatchers.Main
作為它的CoroutineContext
糕档。
我們再來看它的register()
方法
internal class LifecycleCoroutineScopeImpl(
override val lifecycle: Lifecycle,
override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
init {
// in case we are initialized on a non-main thread, make a best effort check before
// we return the scope. This is not sync but if developer is launching on a non-main
// dispatcher, they cannot be 100% sure anyways.
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
coroutineContext.cancel()
}
}
fun register() {
// TODO use Main.Immediate once it is graduated out of experimental.
launch(Dispatchers.Main) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
coroutineContext.cancel()
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel()
}
}
在register
方法中通過經(jīng)典的launch
來創(chuàng)建一個協(xié)程莉恼,而launch
使用到的CoroutineContext
就是CoroutineSope
中的CoroutineContext
。然后在協(xié)程中結(jié)合Jetpack
的Lifecycle
特性來監(jiān)聽Activiyt
的生命周期速那。
如果對
Lifecycle
的使用與特性還不是很了解的俐银,推薦閱讀這篇入門級文章Android Architecture Components Part3:Lifecycle
意思就是說在Activity
銷毀的時候會調(diào)用下面的方法取消協(xié)程的運(yùn)行。
coroutineContext.cancel()
這里就使用到了CoroutineContext
端仰,經(jīng)過上篇文章的分析我們很容易知道CoroutineContext
自身是沒有cancel
方法的捶惜,所以這個cancel
方法是CoroutineContext
的擴(kuò)展方法。
public fun CoroutineContext.cancel(): Unit {
this[Job]?.cancel()
}
所以真正的邏輯是從CoroutineContex
集合中取出Key
為Job
的實(shí)例荔烧,這個對應(yīng)的就是上面創(chuàng)建LifecycleCoroutineScopeImpl
實(shí)例時傳入的SupervisorJob
吱七,它是CoroutineContext
的其中一個子類汽久。
這時再來看lifecycleScope
相關(guān)的一些方法
lifecycleScope.launchWhenCreated { }
lifecycleScope.launchWhenStarted { }
lifecycleScope.launchWhenResumed { }
這些方法的內(nèi)部邏輯就很明顯了,也就是通過Lifecycle
來追蹤Activity
的生命周期踊餐,從而約束協(xié)程運(yùn)行的時機(jī)景醇。
我們也可以不使用lifecycleScope
,自己實(shí)現(xiàn)一個CoroutineScope
吝岭,讓它在Activity
達(dá)到同樣的效果三痰。
class MyActivity : AppCompatActivity(), CoroutineScope {
lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onDestroy() {
super.onDestroy()
job.cancel() // Cancel job on activity destroy. After destroy all children jobs will be cancelled automatically
}
/*
* Note how coroutine builders are scoped: if activity is destroyed or any of the launched coroutines
* in this method throws an exception, then all nested coroutines are cancelled.
*/
fun loadDataFromUI() = launch { // <- extension on current activity, launched in the main thread
val ioData = async(Dispatchers.IO) { // <- extension on launch scope, launched in IO dispatcher
// blocking I/O operation
}
// do something else concurrently with I/O
val data = ioData.await() // wait for result of I/O
draw(data) // can draw in the main thread
}
}
上面的實(shí)現(xiàn)也能夠保證當(dāng)前Activiyt
中的協(xié)程在Activity
銷毀的時候終止協(xié)程的運(yùn)行。
到這里CoroutineScope
的作用就呼之欲出了窜管,它就是用來約束協(xié)程的邊界散劫,能夠很好的提供對應(yīng)的協(xié)程取消功能,保證協(xié)程的運(yùn)行范圍幕帆。
當(dāng)然這又引申出另外一個話題
Job
是什么获搏?
Job
基本上每啟動一個協(xié)程就會產(chǎn)生對應(yīng)的Job
,例如
lifecycleScope.launch {
}
launch
返回的就是一個Job
失乾,它可以用來管理協(xié)程颜凯,一個Job
中可以關(guān)聯(lián)多個子Job
,同時它也提供了通過外部傳入parent
的實(shí)現(xiàn)
public fun Job(parent: Job? = null): Job = JobImpl(parent)
這個很好理解仗扬,當(dāng)傳入parent
時症概,此時的Job
將會作為parent
的子Job
。
既然Job
是來管理協(xié)程的早芭,那么它提供了六種狀態(tài)來表示協(xié)程的運(yùn)行狀態(tài)彼城。
-
New
: 創(chuàng)建 -
Active
: 運(yùn)行 -
Completing
: 已經(jīng)完成等待自身的子協(xié)程 -
Completed
: 完成 -
Cancelling
: 正在進(jìn)行取消或者失敗 -
Cancelled
: 取消或失敗
這六種狀態(tài)Job
對外暴露了三種狀態(tài),它們隨時可以通過Job
來獲取
public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean
所以如果你需要自己來手動管理協(xié)程退个,可以通過下面的方式來判斷當(dāng)前協(xié)程是否在運(yùn)行募壕。
while (job.isActive) {
// 協(xié)程運(yùn)行中
}
一般來說,協(xié)程創(chuàng)建的時候就處在Active
狀態(tài)语盈,但也有特例舱馅。
例如我們通過launch
啟動協(xié)程的時候可以傳遞一個start
參數(shù)
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
如果這個start
傳遞的是CoroutineStart.LAZY
,那么它將處于New
狀態(tài)刀荒〈停可以通過調(diào)用start
或者join
來喚起協(xié)程進(jìn)入Active
狀態(tài)。
下面我們來看一張簡圖缠借,就能很清晰的了解Job
中的六個狀態(tài)間的轉(zhuǎn)化過程干毅。
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| cancel / fail |
| +----------------+
| |
V V
+------------+ finish +-----------+
| Cancelling | --------------------------------> | Cancelled |
+------------+ +-----------+
上面已經(jīng)提及到一個Job
可以有多個子Job
,所以一個Job
的完成都必須等待它內(nèi)部所有的子Job
完成泼返;對應(yīng)的cancel
也是一樣的硝逢。
默認(rèn)情況下,如果內(nèi)部的子Job
發(fā)生異常,那么它對應(yīng)的parent Job
與它相關(guān)連的其它子Job
都將取消運(yùn)行渠鸽。俗稱連鎖反應(yīng)叫乌。
我們也可以改變這種默認(rèn)機(jī)制,Kotlin
提供了SupervisorJob
來改變這種機(jī)制徽缚。這種情況還是很常見的综芥,例如用協(xié)程請求兩個接口,但并不想因?yàn)槠渲幸粋€接口失敗導(dǎo)致另外的接口也不請求猎拨,這時就可以使用SupervisorJob
來改變協(xié)程的這種默認(rèn)機(jī)制膀藐。
使用很簡單,在我們創(chuàng)建CoroutineContext
的時候加入SupervisorJob
即可红省。例如在上面提到過的lifecycleScope
额各,內(nèi)部就使用到了SupervisorJob
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main
)
你也可以嘗試運(yùn)行下面的這個例子,然后將它的SupervisorJob
替換成別的CoroutineContext
再來看下效果吧恃。
fun main() = runBlocking {
val supervisor = SupervisorJob()
with(CoroutineScope(coroutineContext + supervisor)) {
// 啟動第一個子作業(yè)——這個示例將會忽略它的異常(不要在實(shí)踐中這么做O豪病)
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
println("The first child is failing")
throw AssertionError("The first child is cancelled")
}
// 啟動第二個子作業(yè)
val secondChild = launch {
firstChild.join()
// 取消了第一個子作業(yè)且沒有傳播給第二個子作業(yè)
println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
try {
delay(Long.MAX_VALUE)
} finally {
// 但是取消了監(jiān)督的傳播
println("The second child is cancelled because the supervisor was cancelled")
}
}
// 等待直到第一個子作業(yè)失敗且執(zhí)行完成
firstChild.join()
println("Cancelling the supervisor")
supervisor.cancel()
secondChild.join()
}
}
如果有些任務(wù)你并不想被手動取消,可以使用NonCancellable
作為任務(wù)的CoroutineContext
如果需要Job
獲取協(xié)程的返回結(jié)果痕寓,可以通過Deferred
來實(shí)現(xiàn)傲醉,它是Job
的一個子類,所以也擁有Job
所用功能呻率。同時額外提供await
方法來等待協(xié)程結(jié)果的返回硬毕。
Deferred
可以通過CoroutineScope.async
創(chuàng)建。
最后我們再來介紹下Job
的幾個方法礼仗,start
與cancel
就不多說了吐咳,分別是啟動與取消。
invokeOnCompletion
這個方法是Job
的回調(diào)通知元践,當(dāng)Job
執(zhí)行完后會調(diào)用這個方法
public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
public typealias CompletionHandler = (cause: Throwable?) -> Unit
這個cause
有三種情況分別為:
-
is null
: 協(xié)程正常執(zhí)行完畢 -
is CancellationException
: 協(xié)程正常取消韭脊,并非異常導(dǎo)致的取消 -
Otherwise
: 協(xié)程發(fā)生異常
同時它的返回值DisposableHandle
可以用來取消回調(diào)的監(jiān)聽。
join
public suspend fun join()
注意這是一個suspend
函數(shù)单旁,所以它只能在suspend
或者coroutine
中進(jìn)行調(diào)用沪羔。
它的作用是暫停當(dāng)前運(yùn)行的協(xié)程任務(wù),立刻執(zhí)行自身Job
的協(xié)程任務(wù)象浑,直到自身執(zhí)行完畢之后才恢復(fù)之前的協(xié)程任務(wù)繼續(xù)執(zhí)行蔫饰。
本篇文章主要介紹了CoroutineScope
的作用與Job
的相關(guān)狀態(tài)演化與運(yùn)用。希望對學(xué)習(xí)協(xié)程的伙伴們能夠有所幫助融柬,敬請期待后續(xù)的協(xié)程分析死嗦。
項(xiàng)目
android_startup: 提供一種在應(yīng)用啟動時能夠更加簡單柄粹、高效的方式來初始化組件俯渤,優(yōu)化啟動速度敌买。不僅支持Jetpack App Startup
的全部功能态辛,還提供額外的同步與異步等待外盯、線程控制與多進(jìn)程支持等功能摘盆。
AwesomeGithub: 基于Github
客戶端,純練習(xí)項(xiàng)目饱苟,支持組件化開發(fā)孩擂,支持賬戶密碼與認(rèn)證登陸。使用Kotlin
語言進(jìn)行開發(fā)箱熬,項(xiàng)目架構(gòu)是基于Jetpack&DataBinding
的MVVM
类垦;項(xiàng)目中使用了Arouter
、Retrofit
城须、Coroutine
蚤认、Glide
、Dagger
與Hilt
等流行開源技術(shù)糕伐。
flutter_github: 基于Flutter
的跨平臺版本Github
客戶端砰琢,與AwesomeGithub
相對應(yīng)。
android-api-analysis: 結(jié)合詳細(xì)的Demo
來全面解析Android
相關(guān)的知識點(diǎn), 幫助讀者能夠更快的掌握與理解所闡述的要點(diǎn)良瞧。
daily_algorithm: 每日一算法陪汽,由淺入深,歡迎加入一起共勉褥蚯。