文章目錄
-
- 協程啟動
- DEFAULT
- LAZY
- ATOMIC
- UNDISPATCHED
- 附錄
- 參考資料
說了這麼多線程,原因嘛,畢竟大家對它是最熟悉的。協程的 API 設計其實也與之一脈相承,我們來看一段最簡單的啟動協程的方式:
GlobalScope.launch {
//do what you want
}
那麼這段代碼會怎麼執行呢?我們說過,啟動協程需要三樣東西,分别是
上下文
、
啟動模式
協程體
,
協程體
就好比
Thread.run
當中的代碼,自不必說。
本文将為大家詳細介紹 啟動模式。在 Kotlin 協程當中,啟動模式是一個枚舉:
public enum class CoroutineStart {
DEFAULT,
LAZY,
@ExperimentalCoroutinesApi
ATOMIC,
@ExperimentalCoroutinesApi
UNDISPATCHED;
}
模式 | 功能 |
---|---|
立即執行協程體 | |
立即執行協程體,但在開始運作之前無法取消 | |
立即在目前線程執行協程體,直到第一個 suspend 調用 | |
隻有在需要的情況下運作 |
四個啟動模式當中我們最常用的其實是
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
是懶漢式啟動,
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
隻有涉及
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
就很容易了解了。協程在這種模式下會直接開始在目前線程下執行,直到第一個挂起點,這聽起來有點兒像前面的
ATOMIC
,不同之處在于
UNDISPATCHED
不經過任何排程器即開始執行協程體。當然遇到挂起點之後的執行就取決于挂起點本身的邏輯以及上下文當中的排程器了。
fun log(mes: Int) {
Log.e("zhaoyanjun:", "$mes ${Thread.currentThread().name}")
}