Kotlin實戰指南十四:協程啓動模式

轉載請標明出處: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

四個啓動模式當中我們最常用的其實是 DEFAULTLAZY

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 , 歡迎關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章