Kotlin 協程

下面這個博客對協程的講解非常清楚

https://kaixue.io/kotlin-coroutines-1/

kotlin 官方中文資料

https://www.kotlincn.net/docs/reference/

協程的概念

        協程是一套線程框架,是對線程中執行的代碼順序的管理,協程中的代碼依然在線程中運行;協程設計的初衷是爲了解決併發問題,讓協作式多任務實現起來更加方便。協程是一種編程思想,在其他語言中也有實現如Go、Python、Java的Kilim等。

舉個協程的栗子

launch{
    val img = getImage(); // 耗時操作 需要等待
    show(img); // UI 顯示圖像 應在主線程執行
}

{}中的代碼就是一段協程代碼,其中getImage() 方法在子線程中執行,而show方法在主線程中執行,以同步代碼的方式實現了異步邏輯,這段代碼中並未使用回調函數將獲取到的圖像傳遞給show方法,如果都使用這樣的寫法可以避免出現回調地獄場景。

協程的創建

 

協程創建方式 說明 使用範圍
runBlocking{...}

創建新的協程,運行在當前線程上,所以會堵塞當前線程,

直到協程體結束;但是這個runBlocking域中可以有多個協程,

多個協程可以併發進行,不會等待子協程執行結束

用於啓動一個協程任務,通常只用於啓動最外層的協程,

例如線程環境切換到協程環境

GlobalSocpe.launch{...}

啓動一個新的線程,在新線程上創建運行協程,

不堵塞當前線程

需要啓動異步線程處理的情況
CoroutineScope(Dispathcer.xxx).{...} 在指定類型的線程中創建協程,不會阻塞所運行的線程  

 

 

 

 

 

 

 

上面的launch方法可以的並列方法是async方法

  • lauch:協程構建器,創建並啓動(也可以延時啓動)一個協程,返回一個Job,用於監督和取消任務,用於無返回值的場景。
  • async:協程構建器,和launch一樣,區別是返回一個Job的子類 Deferred,async可以在協程體中自定義返回值,並且通過Deferred.await堵塞當前線程等待接收async協程返回的類型。特別是需要啓動異步線程處理並等待處理結果返回的場景

CoroutineScope 創建新一個子域,並管理域中的所有協程。注意這個方法只有在block中創建的所有子協程全部執行完畢後,纔會退出。

SuperVisorScope 在子協程失敗時,錯誤不會往上傳遞給父域,所以不會影響子協程。

線程切換

最常用的方法withContext(Dispatcher.xxx){code}

該方法讓code代碼運行在Dispatcher.xxx指定的線程中,  運行結束後再自動切回到調用withContext的線程。

suspend fun doSomething(){

    withContext(Dispathcer.IO){

        var img = getImage() // 耗時操作, getImag 是掛起函數

    }

}

如在主函數中調用doSomething() 則線程先從主線程切到IO線程,等協程執行完之後再切換回主線程

協程調度器

上述切換協程的運行線程使用了協程的調度器,協程的調度器有如下幾種

調度器
類型 作用 場景
Dispatcher.Main 使協程運行在主線程 更新UI
Dispatcher.IO 使協程運行在IO線程 用於網絡請求和文件訪問
Dispatcher.Default 使用共享線程運行協程 CPU密集型任務
Dispatcher.UnConfined 使用父協程運行的線程 高級調度器,不應該在常規代碼裏使用
newSingleThreadContext 在新線程中運行協程  

 

 

 

 

 

 

 

 

掛起

拋物線的這篇文章對掛起的解釋很清楚  https://kaixue.io/kotlin-coroutines-2/

掛起函數 關鍵字 suspend

suspend 關鍵字用來標識一個函數是掛起函數,但是suspend關鍵字標識的函數內部不一定有協程的掛起操作,如果函數使用了suspend關鍵字則函數只能在協程內或另一個掛起函數中被調用。

什麼是掛起

aunch ,async 或者其他函數創建的協程,在執行到某一個 suspend 函數的時候,這個協程會被「suspend」,也就是被掛起。

那此時又是從哪裏掛起?從當前線程掛起。換句話說,就是這個協程從正在執行它的線程上脫離。

注意,不是這個協程停下來了!是脫離,當前線程不再管這個協程要去做什麼了。

suspend 是有暫停的意思,但我們在協程中應該理解爲:當線程執行到協程的 suspend 函數的時候,暫時不繼續執行協程代碼了。

協程在執行到有 suspend 標記的函數的時候,會被 suspend 也就是被掛起,而所謂的被掛起,就是切個線程;不過區別在於,掛起函數在執行完成之後,協程會重新切回它原先的線程

再簡單來講,在 Kotlin 中所謂的掛起,就是一個稍後會被自動切回來的線程調度操作

當執行到一個掛起函數時,當前線程將不再執行這個掛起函數和該協程後續代碼,直到這個掛起函數執行結束(或者這個掛起函數切回這個線程)。這個掛起函數仍然被執行,但可能在另一個線程中被繼續執行,當然也可能繼續在當前線程執行,根據代碼中設置的調度器而定。

協程同步

存在多個協程時,有些協程直接沒有交互關係,而有些協程需要另一個協程執行完的結果,對於沒有關係的協程可以讓他們並行執行,對於有關係的協程可以讓他們異步執行。

runBlocking {
    Log.d("dd","zero")
    Log.d("dd",Thread.currentThread().name)
    GlobalScope.async(Dispatchers.Main) {
       Log.d("dd", "one")
       delay(200)
       Log.d("dd", "two")
    }  // .await()
    GlobalScope.async(Dispatchers.Main){
         Log.d("dd", "three")
         delay(100)
         Log.d("dd", "four")
    }  //.await()
    Log.d("dd","five")
 }
 Log.d("dd", "six")

 

   one->three->four->two 協程是併發進行的

增加await()方法後的輸出:

  one->two->three->four 協程是按順序執行的,第一個執行完再執行第二個

取消

取消一個協程可以使用cancel()方法Job#cancel()、 Deferred#cancel()、cancelAndrJoin()方法

kotlin提供的掛起函數都是可以取消的,自定義的掛起函數如果想可以被取消可以在掛起函數中判斷isActive狀態,當調用cancel()方法後這個狀態會發生變化。

val job = launch {
    repeat(1000) { i ->
        println("job: I'm sleeping $i ...")
        delay(500L)
    }
}
delay(1300L) // 延遲一段時間
println("main: I'm tired of waiting!")
job.cancel() // 取消該作業
job.join() // 等待作業執行結束
println("main: Now I can quit.")

輸出結果如下:

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.

由於delay函數是coroutine的自帶掛起函數,可取消所以運行到2就結束了。

val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
    var nextPrintTime = startTime
    var i = 0
    while (isActive) { // 可以被取消的計算循環
        // 每秒打印消息兩次
        if (System.currentTimeMillis() >= nextPrintTime) {
            println("job: I'm sleeping ${i++} ...")
            nextPrintTime += 500L
        }
    }
}
delay(1300L) // 等待一段時間
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消該作業並等待它結束
println("main: Now I can quit.")

運行結果同上,這個協程中執行了耗時操作,可以將其抽象成一個掛起函數,注意while中的判斷條件isActive,用來結束協程。如果監聽這個狀態而調用cancel()協程不會被取消知道運行結束。

當一個父協程被取消的時候,所有它的子協程也會被遞歸的取消。然而,當使用 GlobalScope 來啓動一個協程時,則新協程的作業沒有父作業。 因此它與這個啓動的作用域無關且獨立運作。

超時

攜程的執行時間可以受到約束,我們使用withTimeout、withTimeoutOrNull

withTimeout(time)方法如果超時了會爆出一個TimeoutCancelleationException,而withTimeoutOrNull則返回null

狀態

協程的生命週期

 

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