天天看點

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

文章目錄

    • 協程啟動
    • 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}")
 }
           

繼續閱讀