Kotlin Coroutine(協程)簡介

老胡_laohu發表於2019-04-15

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

協程介紹

協程是可掛起計算的例項。

它在概念上類似於執行緒,在這個意義上,它需要一個程式碼塊執行,並具有類似的生命週期,它可以被建立和啟動,但它不繫結到任何特定的執行緒。

它可以在一個執行緒中掛起其執行,並在另一個執行緒中恢復。而且,像future 或 promise那樣,它在完結時可能伴隨著某種結果(值或異常)

協程開發人員這樣描述協程:

協程就像非常輕量級的執行緒。執行緒是由系統排程的,執行緒切換或執行緒阻塞的開銷都比較大。而協程依賴於執行緒,但是協程掛起時不需要阻塞執行緒,幾乎是無代價的,協程是由開發者控制的。所以協程也像使用者態的執行緒,非常輕量級,一個執行緒中可以建立任意個協程。

如上面所說,協程是由開發者自己控制的,因此在使用協程時我們一定要記住一點,我們必須知道我們使用的協程在何時掛起,它又在何時重新恢復執行,如果沒法知道這兩點,那就意味著我們無法控制協程,這個時候要慎用協程。

為什麼使用協程

使用協程可以提高執行緒的利用率。

通常我們在Android中發起一個網路請求都會經歷如下幾步:

  1. 在主執行緒中建立一個請求任務,如:Retrofit.Call
  2. 為這個任務分配一個子執行緒去執行請求任務,如:呼叫Retrofit.Call.enqueue(callback)方法
  3. 子執行緒發起請求後將會阻塞等待網路請求的返回結果,拿到結果後會將資料轉換成我們需要的實體物件
  4. 在主執行緒中執行回撥介面,執行餘下的業務操作

上面的流程中為請求任務分配子執行緒一般都會配合執行緒池去做,以防止不斷建立執行緒而產生系統開銷,但線上程真正執行過程中經常會遇到因磁碟IO或者是網路請求等操作而導致執行緒阻塞,而此時當前執行緒只能阻塞等待,無法做任何事情,在等待的這段時間裡執行緒相當於白白了浪費了自身資源,導致執行緒自身利用率低下。

Android中改用協程發起網路請求流程如下:

  1. 在主執行緒中建立一個協程,在協程中建立網路請求任務
  2. 為協程分配一個子執行緒去發起網路請求
  3. 掛起子執行緒中的協程,此時僅僅是協程掛起,該子執行緒並沒有掛起阻塞
  4. 協程等待請求結果回來之後,會在子執行緒中重新恢復協程執行
  5. 在主執行緒中執行某個回撥,拿到請求資料執行餘下的業務操作

在上述流程步驟3中掛起協程後子執行緒並不會阻塞,此時該子執行緒可以被系統分配去做其他的事情,當協程掛起結束時重新在子執行緒中恢復執行。這樣該執行緒就不會存在因阻塞導致的空閒浪費,提高了執行緒利用率。

總的來說,使用協程可以最大程度的複用執行緒,通過讓執行緒滿載執行,從而達到充分的利用CPU提高系統效能。

告別回撥地獄

使用協程另外一個好處就是可以讓開發者們告別非同步程式設計中的回撥地獄,簡化非同步程式設計,讓寫非同步程式碼和寫同步程式碼一樣簡單,增強了程式碼的可讀性、可理解性和可維護性。

舉個例子
假定有個登入有如下流程:

  1. 發請求獲取使用者token
  2. 根據token獲取使用者資訊

常用實現方式程式碼如下:

fun login() {
    requestToken { token ->
        requestUserInfo(token) { user ->
            Log.i("tag", user.toString())
        }
    }
}
複製程式碼

上面的例子是Android開發中經常會遇到的問題,一個請求依賴前一個請求的結果,這個時候經常會出現這樣的寫法,在第一個請求的成功回撥中根據請求結果發起第二個網路請求。這裡還只存在兩層的巢狀,試想一下,如果巢狀層次出現4次,5次,甚至更多會出現怎樣的情況,估計開發者自己寫起來都會崩潰。

使用RxJava實現程式碼如下:

Single.fromCallable { requestToken() }
    .map { token -> requestUserInfo(tokenm) }
    .subscribe(
        { user -> Log.i("tag", user.toString()) }, // onSuccess
        { e -> e.printStackTrace() }  // onError
    )
複製程式碼

使用RxJava的實現方式雖然將回撥巢狀改成了鏈式寫法,閱讀起來要稍微好點,但是依然存在回撥而且增加了實現的複雜度,對不熟悉RxJava的人來說更少增加了難度。

使用協程實現方式程式碼如下:

fun login() {
    val token = requestToken()
    val user = requestUserInfo(token)
    Log.i("tag", user.toString())
}
複製程式碼

怎麼樣,使用協程的寫法是不是簡便很多,而且看起來非常符合人們的閱讀和理解習慣。

相關文章