Kotlin Coroutine(協程) 基本知識

老胡_laohu發表於2019-04-15

Kotlin Coroutine(協程)系列:
1. Kotlin Coroutine(協程) 簡介
2. Kotlin Coroutine(協程) 基本知識

這篇文章主要介紹協程中的一些基本概念。

掛起函式(suspend關鍵字)

Kotlin中提供了關鍵字suspend用來描述一個函式為掛起函式,寫法如下:

//官方提供的函式
suspend fun delay(timeMillis: Long) {
    ...
}
複製程式碼

以上寫法就代表delay函式為一個掛起函式。

在前面一篇文章Kotlin Coroutine(協程) 簡介中我提到過掛起函式只會掛起當前協程,不會掛起阻塞當前協程所處的執行緒。事實上,想要執行協程就至少需要一個掛起函式,因此掛起函式是協程中一個非常重要的概念。

特點
  1. 掛起函式能用普通函式的方式獲取引數和返回值
  2. 呼叫掛起函式時,可能會掛起當前協程(如果掛起函式的相關呼叫已經有結果,那麼系統可能會選擇不掛起),而不會掛起所在的執行緒。
  3. 掛起函式執行結束後,協程會自動恢復執行,此時才能繼續執行掛起函式後續的程式碼
  4. 掛起函式只能在協程或其他掛起函式中呼叫,否則會編譯報錯
  5. suspend可以將普通函式、擴充套件函式、lambda表示式均標記為掛起函式

CoroutineScope

官方描述:為協程定義了一個範圍

Defines a scope for new coroutines.

也可以理解為協程的上下文環境,更通俗點你可以將其看作為一個協程。

我們再來看下官方原始碼中的定義:

public interface CoroutineScope {
    /**
     * Context of this scope.
     */
    public val coroutineContext: CoroutineContext
}
複製程式碼

通過這個程式碼我們可以看到CoroutineScope初始定義中只有一個協程上下文CoroutineContext物件,所以協程的上下文物件其實是由CoroutineContext決定的,因此將CoroutineScope看作協程更好理解。

CoroutineContext

協程上下文,包含了協程中的一些元素,主要有JobCoroutineDispatcher

Job

協程的後臺任務,它有自己的生命週期,該任務可以被取消。

Job可以有父Job,當父Job被取消時,其所有子Job也會被取消。

Job有三種狀態:

  1. isActive 是否處於活動狀態
  2. isCompleted 是否完成
  3. isCancelled 是否被取消

可參考下表:

State [isActive] [isCompleted] [isCancelled]
New (optional initial state) false false false
Active (default initial state) true false false
Completing (transient state) true false false
Cancelling (transient state) false false true
Cancelled (final state) false true true
Completed (final state) false true false

當建立協程開始執行並獲取到Job物件後,如果想等該協程執行結束再執行其他的業務邏輯,那麼可以呼叫Job.join()方法,該方法會等待該協程任務執行結束,該方法為掛起函式。

Deferred

它是Job的子類,與Job不同的是它可以有返回值,而Job是沒有返回值的。

通過呼叫Deferredawait()方法即可拿到返回值,而await()方法也是一個掛起函式,因此呼叫該方法時會掛起當前協程,直到拿到返回值協程重新恢復執行。

Android中協程結合Retrofit發起網路請求可以考慮使用該類獲取請求結果

CoroutineDispatcher

協程排程器,它可以將協程的執行侷限在指定的執行緒中,它有四個預設的實現:

  1. Dispatchers.Default 預設排程器,在使用launchasync等協程構造器建立協程時,如果不指定排程器則會使用此預設排程器,該排程器會讓協程在JVM提供的共享執行緒池中執行
  2. Dispatchers.Main 主執行緒排程器,讓協程在主執行緒即UI執行緒中執行
  3. Dispatchers.IO 讓協程在IO執行緒(子執行緒)中執行,該排程器會與Dispatchers.Default排程器共享同一個執行緒池
  4. Dispatchers.Unconfined 該排程器不指定協程在某個執行緒中執行。設定了該排程器的協程會在呼叫者執行緒中啟動執行直到第一個掛起點,掛起後,它將在掛起函式執行的執行緒中恢復,恢復的執行緒完全取決於該掛起函式在哪個執行緒執行。
  5. newSingleThreadContext 這是Kotlin另外提供的一個排程器,它會為協程啟動一個新的執行緒。一個專用的執行緒是一種非常昂貴的資源。 在真實的應用程式中兩者都必須被釋放,當不再需要的時候,使用 close 函式,或儲存在一個頂級變數中使它在整個應用程式中被重用。

另外需要注意的是:協程排程器預設承襲外部協程的排程器。

GlobalScope

這是一個全域性的CoroutineScope不會受任何Job約束,通過它建立的是全域性協程,它會在整個應用的生命週期中執行,不能被取消

launch函式

這是一個擴充套件的CoroutineScope例項方法,同時也是一個很常用的協程構建器。

通過其預設引數會建立一個不會阻塞當前執行緒且會立即執行的協程,該方法會返回一個Job物件,該方法預設承襲所在的CoroutineScope物件的排程器。

val scope = CoroutineScope(Dispatchers.Main + Job())
scrope.launch {
    //協程實現
}
複製程式碼

上述程式碼通過launch建立的協程會在UI執行緒中執行

val scope = CoroutineScope(Dispatchers.Main + Job())
scrope.launch(Dispatchers.IO) {
    //協程實現
}
複製程式碼

上述程式碼通過launch建立的協程會在IO執行緒中執行

runBlocking

這是一個全域性的協程構建器,可以在任何地方呼叫。

該構建器會建立一個阻塞當前執行緒的協程,所以該構建器不建議使用在協程內。

async

launch函式一樣,也是CoroutineScope的擴充套件例項方法,它也是一個常用的協程構建器,不同是它建立協程時返回的是Deferred,通過Deferred可以拿到執行結果

val a = async {
    log("I'm computing a piece of the answer")
    6
}
val b = async {
    log("I'm computing another piece of the answer")
    7
}
log("The answer is ${a.await() * b.await()}")
複製程式碼

delay

全域性函式

  1. 讓協程休眠指定時間,類似於Java中的Thread.sleep的作用
  2. delay是一個掛起函式,呼叫後不會阻塞掛起當前執行緒
  3. 當協程的休眠時間到了之後,當前所處協程會重新恢復執行

withContext

切換協程上下文,一般主要用來切換協程所在的執行緒環境,如從主執行緒切換到IO執行緒。

呼叫該方法不會建立新的協程,同時是一個掛起函式

該方法會有一個返回值,其返回值為withContext中lambda表示式的返回值

相關文章