揭開Kotlin協程的神秘面紗

banq發表於2018-08-08
Kotlin協程提供了一種新的非同步執行方式,但直接檢視庫函式可能會有點混亂,本文中嘗試揭開協程的神秘面紗。

讓我們從基礎開始吧,假設有一個名為launch可以用來啟動協程

private fun myHeavyFunction() {
    Log.e("Thread Running ", Thread.currentThread().name)
}
val job = launch { myHeavyFunction() }
<p class="indent">


上面的程式碼是使用launch一種非常簡單的方法,返回Job一個非同步執行函式,Job代表一個協程coroutine作業,可以取消或查詢它的狀態。

override fun onStop() {
    if (job.isActive) {
        job.cancel()
    }
}
<p class="indent">



現在,如果檢視我們的日誌,檢查我們的函式實際執行的是哪個執行緒?我們就會得到類似的結果

E / Thread執行:ForkJoinPool.commonPool-worker-2

我們的程式碼是在一個執行緒中執行的,讓我們稍微瞭解一下launch本身:

public fun launch(
  context:CoroutineContext =DefaultDispatcher,
  start:CoroutineStart CoroutineStart.DEFAULT,
  parent:Job?=null,
  onComp1etion:CompletionHand1er? =null,
  block:suspend CoroutineScope.()->Unit
):Job{		
		

<p class="indent">


再看看DefaultDispatcher的值是什麼?

@Suppress("PropertyName ") 
public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool

object CommonPool:CoroutineDispatcher()

<p class="indent">


launch是將CoroutineContext作為第一個引數,這個引數值預設為代表一個CommonPool執行緒池類的DefaultDispatcher,這個執行緒池類根據當前CPU處理器總數建立一個帶有Executors的CoroutineContext。完整程式碼在這裡


launch是一種協程構建器,可以接受一個協程分配器CoroutineDispatcher,分配器實際上負責在單獨的執行緒中執行程式碼。

我們可以輕鬆建立自己的分配器:

val singleThreadDispatcher = newSingleThreadContext("singleThreadDispatcher")
<p class="indent">


newSingleThreadContext 由Kotlin協同程式庫本身提供,用於建立僅在單個執行緒上執行的上下文。我們可以在此基礎上建立自己的函式:

fun <T> singleThreadAsync(block: () -> T): Job = launch(singleThreadDispatcher) { block.invoke() }

job = singleThreadAsync { myHeavyFunction() }
<p class="indent">


下面是執行後的日誌

E / Thread執行:singleThreadDispatcher

所以我們用我們自己的執行緒方案建立了我們自己的簡單協程:)

讓我們看看我們可以透過Dispatchers做更多事情:

object MyDispatcher : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        thread {
            block.run()
        }
    }
}

object RxDispatcher : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        Observable.fromCallable { block.run() }
                .subscribeOn(Schedulers.io())
                .subscribe {}

    }
}

object UIDispatcher : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        Handler(Looper.getMainLooper()).post {
            block.run()
        }
    }
}
<p class="indent">


在這裡,我們建立了三個不同的分配器程式並過載了dispatch方法, 我們在每個dispatch方法中以不同的方式執行Runnable塊,也就是一個簡單的執行緒,這個非同步執行緒是使用RxJava實現,而Android主執行緒是使用Handler完成。

如果我們用這些分配器程式執行我們的函式,我們會得到這些日誌

E / Thread Running:Thread-582
E / Thread Running:RxCachedThreadScheduler-1
E / Thread Running:main

這真的顯示了協同程式的強大功能,因為Coroutines只是語言語法,它們與執行它們的平臺無關。不同執行緒的職責分配只需開發人員使用一組函式就能實現,他可以在Rx執行緒或主執行緒上執行他喜歡的協同程式。

協同程式就像空的冰淇淋甜筒,你可以選擇你想要冰淇淋的填入。

無執行緒Thread-less非同步
編寫非同步程式碼傳統上被認為是一種執行緒工作,其實並不總是如此,讓我們看看如何使用Coroutines解決這個問題

讓我們看看一系列函式執行

mySmallFunction1() 
myHeavyFunction() // Takes 3 seconds to execute
mySmallFunction2()
//Order執行順序
E/mySmallFunction1 running on: main
E/myHeavyFunction running on: main
E/mySmallFunction2 running on: main
<p class="indent">


現在因為myHeavyFunction()函式需要很長時間才能執行,所以我們可能想要非同步執行它。

mySmallFunction1()
thread { myHeavyFunction() } //Execution in a separate thread.
mySmallFunction2()
//Order順序
E/mySmallFunction1 running on: main
E/mySmallFunction2 running on: main
E/myHeavyFunction running on: Thread-697
<p class="indent">


這裡我們將myHeavyFunction()遷移到一個單獨的執行緒並非同步執行它,但是如果我們這樣做:

mySmallFunction1()
launch(UI) { myHeavyFunction() }
mySmallFunction2()
//Order
E/mySmallFunction1 running on: main
E/mySmallFunction2 running on: main
E/myHeavyFunction running on: main
<p class="indent">


這裡我們在主執行緒上執行的Coroutine上下文(UI:由coroutine-android庫提供)中執行重量函式,執行仍然是非同步的,因為Coroutines是透過暫停這部分函式處理,但函式執行仍然發生在主執行緒上,而不建立額外的執行緒。

實戰協程
在大多數情況下,我們需要來自一個非同步執行的回撥,這樣我們就可以透過回撥函式來更新UI等,這裡就可以使用Deferred語法:


Deferred本身繼承擴充套件了Job,但增加一個額外的功能,它可以在函式完成執行後返回未來的值。


讓我們看看我們在這裡做了什麼:

fun <T> asyncExecutor(block: () -> T, response: (T) -> Unit): Job {
    return launch(UI) {
        val deferred = async(singleThreadDispatcher) { block.invoke() }
        response.invoke(deferred.await())
    }
}
<p class="indent">

讓我們分析一下:

1. launch(UI)使用Android的UI所在的執行緒上下文建立一個協同Job。
2. 我們透過async非同步建立了另一個協同程式,其中包含我們需要呼叫的函式,唯一的區別是:這個協程返回一個Deferred值,async是協程庫的一部分。
3. 我們呼叫await()函式來捕獲Deferred的未來值。這是在UI所線上程上下文中捕獲的。

總而言之,我們建立了一個非同步執行程式,我們可以在其中傳遞函式並讓它們非同步執行,然後將值返回給UI執行緒。

現在我們在哪裡可以使用它 ? 資料庫查詢

// Insert into DB without callback
singleThreadAsync { movieDataBase.movieDao().insert(movieObject) }

// Get List of movies from DB and filter it
asyncExecutor({ movieDataBase.movieDao().getAll() }, { movieList ->
    movieList
            .filter { it.isFavorite }
            .map { it.originalLanguage = "English" }
    //Dispatch to UI
})
<p class="indent">


我們將插入到DB的請求變成了一個發射就可以忘記不用等待結果的非同步請求,這是使用singleThreadAsync實現的 。

當我們從DB檢索資料時,我們可以使用我們的asyncExecutor來檢索物件列表,然後使用Collection Framework中的運算子發揮所有kotlin優點啦!

Demystifying Kotlin Coroutines – ProAndroidDev

相關文章