Kotlin Coroutines 筆記 (二)

躲雨的MM.jpg

協程雖然是微線程,可是並不會和某一個特定的線程綁定,它能夠在A線程中執行,並通過某一個時刻的掛起(suspend),等下次調度到恢復執行的時候,極可能會在B線程中執行。java

一. withContext

與 launch、async、runBlocking 相似 withContext 也屬於 Coroutine builders。不過與他們不一樣的是,其餘幾個都是建立一個新的協程,而 withContext 不會建立新的協程。withContext 容許更改協程的執行線程,withContext 在使用時須要傳遞一個 CoroutineContext 。安全

launch {

        val result1 = withContext(CommonPool) {

            delay(2000)
            1
        }

        val  result2 = withContext(CommonPool) {

            delay(1000)
            2
        }

        val  result = result1 + result2
        println(result)
    }

    Thread.sleep(5000)
複製代碼

執行結果:bash

3
複製代碼

withContext 能夠有返回值,這一點相似 async。async 建立的協程經過 await() 方法將值返回。而 withContext 能夠直接返回。app

launch {

        val result1 = async {

            delay(2000)
            1
        }

        val  result2 = async {

            delay(1000)
            2
        }

        val  result = result1.await() + result2.await()
        println(result)
    }

    Thread.sleep(5000)
複製代碼

執行結果:async

3
複製代碼

二. 共享線程池

在上述的例子中,withContext 使用了 CommonPool。CommonPool 繼承了 CoroutineDispatcher,表示使用線程池來執行協程的任務。ide

CommonPool.png

CommonPool 有點相似於 RxJava 的 Schedulers.computation(),主要是用於CPU密集型的計算任務。函數

CommonPool 使用 pool 來執行 block。oop

override fun dispatch(context: CoroutineContext, block: Runnable) =
        try { (pool ?: getOrCreatePoolSync()).execute(timeSource.trackTask(block)) }
        catch (e: RejectedExecutionException) {
            timeSource.unTrackTask()
            DefaultExecutor.execute(block)
        }
複製代碼

若是 pool 爲空,則調用 getOrCreatePoolSync() 方法來建立 pool。post

@Synchronized
    private fun getOrCreatePoolSync(): Executor =
        pool ?: createPool().also { pool = it }
複製代碼

此時,createPool() 方法是正在建立 pool 的方法。ui

首先,安全管理器不爲空的話,使用 createPlainPool() 來建立 pool。 不然,嘗試建立一個 ForkJoinPool,不行的話仍是使用 createPlainPool() 來建立 pool。

private fun createPool(): ExecutorService {
        if (System.getSecurityManager() != null) return createPlainPool()
        val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") }
            ?: return createPlainPool()
        if (!usePrivatePool) {
            Try { fjpClass.getMethod("commonPool")?.invoke(null) as? ExecutorService }
                ?.let { return it }
        }
        Try { fjpClass.getConstructor(Int::class.java).newInstance(parallelism) as? ExecutorService }
            ?. let { return it }
        return createPlainPool()
    }
複製代碼

createPlainPool() 會使用 Executors.newFixedThreadPool() 來建立線程池。

private fun createPlainPool(): ExecutorService {
        val threadId = AtomicInteger()
        return Executors.newFixedThreadPool(parallelism) {
            Thread(it, "CommonPool-worker-${threadId.incrementAndGet()}").apply { isDaemon = true }
        }
    }
複製代碼

CommonPool 的建立原理大體瞭解以後,經過源碼發現 CoroutineContext 默認的 CoroutineDispatcher 就是 CommonPool。

/** * This is the default [CoroutineDispatcher] that is used by all standard builders like * [launch], [async], etc if no dispatcher nor any other [ContinuationInterceptor] is specified in their context. * * It is currently equal to [CommonPool], but the value is subject to change in the future. */
@Suppress("PropertyName")
public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool
複製代碼

常見的 CoroutineDispatcher 還能夠經過 ThreadPoolDispatcher 的 newSingleThreadContext()、newFixedThreadPoolContext()來建立,以及Executor 的擴展函數 asCoroutineDispatcher() 來建立。

在 Android 中,還可使用UI。它顧名思義,在 Android 主線程上調度執行。

三. 可取消的協程

Job、Deferred 對象均可以取消任務。

3.1 cancel()

使用 cancel() 方法:

val job = launch {
        delay(1000)
        println("Hello World!")
    }
    job.cancel()
    println(job.isCancelled)
    Thread.sleep(2000)
複製代碼

執行結果:

true
複製代碼

true表示job已經被取消了,並無打印"Hello World!"

3.2 cancelAndJoin()

使用 cancelAndJoin() 方法:

runBlocking<Unit> {

        val job = launch {

            repeat(100) { i ->
                println("count time: $i")
                delay(500)
            }
        }
        delay(2100)
        job.cancelAndJoin()
    }
複製代碼

執行結果:

count time: 0
count time: 1
count time: 2
count time: 3
count time: 4
複製代碼

cancelAndJoin() 等價於使用了 cancel() 和 join()。

join() 方法用於等待已啓動協程的完成,而且它不會傳播其異常。 可是,崩潰的子協程也會取消其父協程,並帶有相應的異常。

3.3 檢查協程的取消標記

若是一個協程一直在執行計算,沒有去檢查取消標記,它就沒法取消。即便調用了cancel() 或者 cancelAndJoin()。

runBlocking<Unit> {

        val startTime = System.currentTimeMillis()
        val job = launch {
            var tempTime = startTime
            var i = 0
            while (i < 100) {

                if (System.currentTimeMillis() >= tempTime) {
                    println("count time: ${i++}")
                    tempTime += 500L
                }
            }
        }
        delay(2100)
        job.cancelAndJoin()
    }
複製代碼

上述代碼仍然會打印100次。

若是使用isActive檢查取消標記,則Job 或 Deferred 的任務能夠被取消:

runBlocking<Unit> {

        val startTime = System.currentTimeMillis()
        val job = launch {
            var tempTime = startTime
            var i = 0
            while (isActive) {

                if (System.currentTimeMillis() >= tempTime) {
                    println("count time: ${i++}")
                    tempTime += 500L
                }
            }
        }
        delay(2100)
        job.cancelAndJoin()
    }
複製代碼

執行結果:

count time: 0
count time: 1
count time: 2
count time: 3
count time: 4
複製代碼

isActive 是 CoroutineScope 的屬性:

package kotlinx.coroutines.experimental

import kotlin.coroutines.experimental.*
import kotlin.internal.*

/** * Receiver interface for generic coroutine builders, so that the code inside coroutine has a convenient * and fast access to its own cancellation status via [isActive]. */
public interface CoroutineScope {
    /** * Returns `true` when this coroutine is still active (has not completed and was not cancelled yet). * * Check this property in long-running computation loops to support cancellation: * ``` * while (isActive) { * // do some computation * } * ``` * * This property is a shortcut for `coroutineContext.isActive` in the scope when * [CoroutineScope] is available. * See [coroutineContext][kotlin.coroutines.experimental.coroutineContext], * [isActive][kotlinx.coroutines.experimental.isActive] and [Job.isActive]. */
    public val isActive: Boolean

    /** * Returns the context of this coroutine. * * @suppress: **Deprecated**: Replaced with top-level [kotlin.coroutines.experimental.coroutineContext]. */
    @Deprecated("Replace with top-level coroutineContext", replaceWith = ReplaceWith("coroutineContext", imports = ["kotlin.coroutines.experimental.coroutineContext"]))
    @LowPriorityInOverloadResolution
    @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
    public val coroutineContext: CoroutineContext
}
複製代碼

總結:

本文介紹了三個部分:withContext的使用,CommonPool的建立以及如何取消協程。其中,還捎帶介紹了 async 和 await 的使用。

該系列的相關文章:

Kotlin Coroutines 筆記 (一)


Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公衆號二維碼並關注,期待與您的共同成長和進步。

相關文章
相關標籤/搜索