轉載請標明出處:https://blog.csdn.net/zhaoyanjun6/article/details/96008400
本文出自【趙彥軍的博客】
協程啓動
說了這麼多線程,原因嘛,畢竟大家對它是最熟悉的。協程的 API 設計其實也與之一脈相承,我們來看一段最簡單的啓動協程的方式:
GlobalScope.launch {
//do what you want
}
那麼這段代碼會怎麼執行呢?我們說過,啓動協程需要三樣東西,分別是 上下文
、啓動模式
、協程體
,協程體
就好比 Thread.run
當中的代碼,自不必說。
本文將爲大家詳細介紹 啓動模式。在 Kotlin 協程當中,啓動模式是一個枚舉:
public enum class CoroutineStart {
DEFAULT,
LAZY,
@ExperimentalCoroutinesApi
ATOMIC,
@ExperimentalCoroutinesApi
UNDISPATCHED;
}
模式 | 功能 |
---|---|
DEFAULT | 立即執行協程體 |
ATOMIC | 立即執行協程體,但在開始運行之前無法取消 |
UNDISPATCHED | 立即在當前線程執行協程體,直到第一個 suspend 調用 |
LAZY | 只有在需要的情況下運行 |
DEFAULT
四個啓動模式當中我們最常用的其實是 DEFAULT
和 LAZY
。
DEFAULT
是餓漢式啓動,launch
調用後,會立即進入待調度狀態,一旦調度器 OK
就可以開始執行。我們來看個簡單的例子:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch(start = CoroutineStart.DEFAULT) {
log(1)
delay(1000)
log(3)
}
log(2)
}
}
輸出結果是:
E/zhaoyanjun:: 2 main
E/zhaoyanjun:: 1 DefaultDispatcher-worker-2
E/zhaoyanjun:: 3 DefaultDispatcher-worker-2
LAZY
LAZY
是懶漢式啓動,launch
後並不會有任何調度行爲,協程體也自然不會進入執行狀態,直到我們需要它執行的時候。這其實就有點兒費解了,什麼叫我們需要它執行的時候呢?就是需要它的運行結果的時候,launch
調用後會返回一個 Job
實例,對於這種情況,我們可以:
- 調用
Job.start
,主動觸發協程的調度執行 - 調用
Job.join
,隱式的觸發協程的調度執行
所以這個所謂的”需要“,其實是一個很有趣的措辭,後面你還會看到我們也可以通過 await
來表達對 Deferred
的需要。這個行爲與 Thread.join
不一樣,後者如果沒有啓動的話,調用 join
不會有任何作用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
log(1)
delay(1000)
log(2)
}
log(3)
job.start()
log(4)
}
}
執行結果:
E/zhaoyanjun:: 3 main
E/zhaoyanjun:: 4 main
E/zhaoyanjun:: 1 DefaultDispatcher-worker-1
E/zhaoyanjun:: 2 DefaultDispatcher-worker-1
ATOMIC
ATOMIC
只有涉及 cancel
的時候纔有意義,cancel
本身也是一個值得詳細討論的話題,在這裏我們就簡單認爲 cancel 後協程會被取消掉,也就是不再執行了。那麼調用cancel
的時機不同,結果也是有差異的,例如協程調度之前、開始調度但尚未執行、已經開始執行、執行完畢等等。
爲了搞清楚它與 DEFAULT
的區別,我們來看一段例子:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
log(1)
delay(1000)
log(2)
}
log(3)
job.cancel()
log(4)
}
}
執行結果:
E/zhaoyanjun:: 3 main
E/zhaoyanjun:: 4 main
E/zhaoyanjun:: 1 DefaultDispatcher-worker-1
我們創建了協程後立即cancel
,但由於是 ATOMIC
模式,因此協程一定會被調度,因此 1、3、4 一定都會輸出。
從輸出結果看不出什麼,現在我們用 DEFAULT
模式來執行一遍
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val job = GlobalScope.launch(start = CoroutineStart.DEFAULT) {
log(1)
delay(1000)
log(2)
}
log(3)
job.cancel()
log(4)
}
}
執行結果:
E/zhaoyanjun:: 3 main
E/zhaoyanjun:: 4 main
對應的,如果是DEFAULT
模式,在第一次調度該協程時如果 cancel
就已經調用,那麼協程就會直接被 cancel
而不會有任何調用。
需要注意的是,cancel
調用一定會將該 job
的狀態置爲 cancelling
,只不過ATOMIC
模式的協程在啓動時無視了這一狀態。
我們使用線程的時候,想要讓線程裏面的任務停止執行也會面臨類似的問題,但遺憾的是線程中看上去與 cancel
相近的 stop
接口已經被廢棄,因爲存在一些安全的問題。不過隨着我們不斷地深入探討,你就會發現協程的 cancel
某種意義上更像線程的 interrupt
。
UNDISPATCHED
有了前面的基礎,UNDISPATCHED
就很容易理解了。協程在這種模式下會直接開始在當前線程下執行,直到第一個掛起點,這聽起來有點兒像前面的 ATOMIC
,不同之處在於 UNDISPATCHED
不經過任何調度器即開始執行協程體。當然遇到掛起點之後的執行就取決於掛起點本身的邏輯以及上下文當中的調度器了。
附錄
log 代碼
fun log(mes: Int) {
Log.e("zhaoyanjun:", "$mes ${Thread.currentThread().name}")
}
參考資料
破解 Kotlin 協程(2) - 協程啓動篇 https://www.bennyhuo.com/2019/04/08/coroutines-start-mode/
個人微信號:zhaoyanjun125 , 歡迎關注