Kotlin系列——在Android中使用協(xié)程以及協(xié)程源碼分析

本文章已授權微信公眾號鴻洋(hongyangAndroid)轉載。

本文章講解的內容是在在Android中使用協(xié)程以及協(xié)程源碼分析

在說協(xié)程之前,我先說下線程線程池

線程操作系統(tǒng)內核資源,是CPU調度的最小單位,所有的應用程序都運行在線程上,它是我們實現并發(fā)異步基礎。在JavaAPI中,Thread是實現線程基礎類,每創(chuàng)建一個Thread對象,操作系統(tǒng)內核就會啟動一個線程,在Thread的源碼中,它的內部實現是大量JNI調用,因為線程的實現必須由操作系統(tǒng)提供直接支持,在Linux操作系統(tǒng)中,每一個Java thread對應一個native thread,它們是一一對應的,在Android中,創(chuàng)建Thread的過程中都會調用Linux API中的pthread_create函數。

線程調用會有存在以下問題:

  • 線程不是輕量級資源,大量創(chuàng)建線程會消耗系統(tǒng)大量資源,傳統(tǒng)的阻塞調用會導致系統(tǒng)存在大量的因為阻塞不能運行線程,這很浪費系統(tǒng)資源
  • 線程阻塞狀態(tài)運行狀態(tài)的切換會存在相當大的開銷,一直以來都是個優(yōu)化點,例如:JVM在運行時會對進行優(yōu)化,就像自旋鎖、鎖粗化鎖消除等等。

線程池(Thread Pool)是一種基于池化思想管理線程的工具,使用線程池有如下好處:

  • 降低資源消耗:通過池化技術重復利用已創(chuàng)建線程,降低線程創(chuàng)建銷毀的損耗。
  • 提高響應速度:任務到達時,無需等待線程創(chuàng)建即可立即執(zhí)行。
  • 提高線程的可管理性線程稀缺資源,如果無限制創(chuàng)建,不僅會消耗系統(tǒng)資源,還會因為線程不合理分布導致資源調度失衡,降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配、調優(yōu)監(jiān)控
  • 提供更多更強大的功能線程池具備可拓展性,允許開發(fā)人員向其中增加更多的功能。

協(xié)程線程有什么關系呢?在Java中,協(xié)程是基于線程池的API,它并沒有脫離Java或者Kotlin已經有的東西。

協(xié)程的定義

協(xié)程源自SimulaModula-2語言,它是一種編程思想,并不局限于特定的語言,在1958年的時候,Melvin Edward Conway提出這個術語并用于構建匯編程序。在Android中使用它可以簡化異步執(zhí)行的代碼,它是在版本1.3中添加到Kotlin。

協(xié)程的使用

下面來介紹如何使用協(xié)程

依賴

要使用協(xié)程,需要在build.gradle文件中添加如下依賴

項目根目錄的build.gradle文件

// build.gradle(AndroidGenericFramework)
ext {
    // 省略部分代碼
    kotlinxCoroutinesVersion = '1.3.1'
    // 省略部分代碼
}

module的build.gradle文件

// build.gradle(:app)
dependencies {
    // 省略部分代碼
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinxCoroutinesVersion"
    // 省略部分代碼
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
    // 省略部分代碼
}
  • org.jetbrains.kotlinx:kotlinx-coroutines-core協(xié)程核心庫,它是協(xié)程公共API,有了這一層公共代碼才能使協(xié)程在各個平臺的接口得到統(tǒng)一
  • org.jetbrains.kotlinx:kotlinx-coroutines-android協(xié)程當前平臺對應的平臺庫,當前平臺是Android,它是協(xié)程具體平臺具體實現,因為類似多線程在各個平臺的實現方式是有差異的。
  • org.jetbrains.kotlinx:kotlinx-coroutines-test協(xié)程測試庫,它方便我們在測試中使用協(xié)程。

這里要注意的是,這三個庫版本保持一致

基礎

下面是協(xié)程基礎部分

啟動協(xié)程

可以通過以下兩種方式來啟動協(xié)程

  • launch可以啟動新協(xié)程,但是不將結果返回給調用方。
  • async可以啟動新協(xié)程,并且允許使用await暫停函數返回結果。

通常我們使用launch函數從常規(guī)函數啟動新協(xié)程,如果要執(zhí)行并行分解的話才使用async函數。

async函數可以返回結果,代碼如下所示:

// Builders.common.kt
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

async函數返回的是Deferred接口,繼承Job接口,它是一個非阻塞、可取消future。

要注意的是launch函數和async函數以不同的方式處理異常,在使用async函數時候可以調用await函數得到結果,如果出現異常將會以靜默方式拋出,也就是說不會出現在崩潰指標中,也不會在logcat中注明。

await函數是針對單個協(xié)程的,awaitAll函數是針對多個協(xié)程的,它們都能保證這些協(xié)程返回結果之前完成。

通常協(xié)程三種方式創(chuàng)建,如下所示:

runBlocking

使用runBlocking頂層函數來創(chuàng)建協(xié)程,這種方式是線程阻塞的,適用于單元測試,一般業(yè)務開發(fā)不會使用這種,示例代碼如下所示:

runBlocking {
    login()
}

runBlocking函數源碼如下所示:

// Builders.kt
@Throws(InterruptedException::class)
// 第一個參數context是協(xié)程上下文,默認值為EmptyCoroutineContext,第二個參數是帶有CoroutineScope接受者對象,不接受任何參數返回T的Lambda表達式
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
    val currentThread = Thread.currentThread()
    val contextInterceptor = context[ContinuationInterceptor]
    val eventLoop: EventLoop?
    val newContext: CoroutineContext
    if (contextInterceptor == null) {
        // 如果沒有指定調度程序(dispatcher),就創(chuàng)建或者使用私有事件循環(huán)
        eventLoop = ThreadLocalEventLoop.eventLoop
        newContext = GlobalScope.newCoroutineContext(context + eventLoop)
    } else {
        // 看看上下文(context)的攔截器(interceptor)是否是一個我們將要使用的事件循環(huán)(用來支持TestContext)或者如果存在thread-local事件循環(huán),就使用它來避免阻塞,不過它不會去新建一個
        eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
            ?: ThreadLocalEventLoop.currentOrNull()
        newContext = GlobalScope.newCoroutineContext(context)
    }
    val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine.joinBlocking()
}

GlobalScope

使用GlobalScope單例對象,并且調用launch函數來創(chuàng)建協(xié)程,這種方式不會阻塞線程,但是不推薦Android中使用這種方式,因為它的生命周期整個應用程序生命周期,如果處理不好,容易導致內存泄漏,而且不能取消,示例代碼如下所示:

GlobalScope.launch {
    login()
}

GlobalScope源碼如下所示:

// CoroutineScope.kt
public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

EmptyCoroutineContext是一個空的協(xié)程上下文

CoroutineScope

使用CoroutineScope對象,并且調用launch函數來創(chuàng)建協(xié)程,這種方式可以通過傳入的CoroutineContext來控制協(xié)程生命周期,推薦使用這種方式,示例代碼如下所示:

CoroutineScope(Dispatchers.IO).launch {
    login()
}

Dispatchers.IOCoroutineContext其中一種類型,下面會講到這個。

CoroutineScope可以管理一個或者多個相關的協(xié)程,可以使用它在指定范圍內啟動新協(xié)程。

與調度程序不同,CoroutineScope不運行協(xié)程。

CoroutineScope的一項重要功能就是在用戶離開你應用中的內容區(qū)域停止執(zhí)行協(xié)程,它可以確保所有正在運行的操作都能正確停止。

CoroutineScope源碼如下所示:

// CoroutineScope.kt
// 參數block是帶有CoroutineScope接受者對象,不接受任何參數返回R的Lambda表達式
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }

在Android中使用協(xié)程

Android平臺上,協(xié)程有助于解決兩個主要問題:

  • 管理長時間運行的任務,如果管理不當,這些任務可能會阻塞主線程并導致你的應用界面凍結
  • 提供主線程安全性,或者從主線程安全地調用網絡或者磁盤操作

管理長時間運行的任務

Android平臺上,每個應用都有一個用于處理界面并且管理用戶交互主線程。如果你的應用為主線程分配的工作太多,會導致界面呈現速度緩慢或者界面凍結,對觸摸事件的響應速度很慢,例如:網絡請求、JSON解析、寫入或者讀取數據庫、遍歷大型列表,這些都應該在工作線程完成。

協(xié)程在常規(guī)函數的基礎上添加了兩項操作,用于處理長時間運行的任務。在invoke或者callreturn之外,協(xié)程添加了suspendresume

  • suspend用于暫停執(zhí)行當前協(xié)程,并保存所有的局部變量。
  • resume用于讓已暫停協(xié)程從其暫停處繼續(xù)執(zhí)行。

要調用suspend函數,只能從其他suspend函數進行調用,或者通過使用協(xié)程構建器(例如:launch)來啟動新的協(xié)程。

Kotlin使用堆棧幀來管理要運行哪個函數以及所有的局部變量。暫停協(xié)程時會復制并保存當前的堆棧幀以供稍后使用;恢復協(xié)程時會將堆棧幀從其保存位置復制回來,然后函數再次開始運行。

編譯器會在編譯期間對被suspend修飾符修飾的函數進行續(xù)體傳遞風格(CPS)變換,它會改變suspend函數函數簽名,我舉個例子:

await函數是個suspend函數,函數簽名如下所示:

suspend fun <T> CompletableFuture<T>.await(): T

編譯期間進行續(xù)體傳遞風格(CPS)變換后:

fun <T> CompletableFuture<T>.await(continuation: Continuation<T>): Any?

我們可以看到進行續(xù)體傳遞風格(CPS)變換后的函數多了一個類型為Continuation<T>的參數,Continuation代碼如下所示:

interface Continuation<in T> {
    val context: CoroutineContext
    fun resumeWith(result: Result<T>)
}

續(xù)體包裝了協(xié)程掛起之后繼續(xù)執(zhí)行的代碼,在編譯過程中,一個完整的協(xié)程被分割成一個又一個續(xù)體,在await函數掛起結束之后,它會調用參數continuationresumeWith函數來恢復執(zhí)行await之后的代碼。

進行續(xù)體傳遞風格(CPS)變換后的函數返回值是Any?,這是因為這個函數發(fā)生變換后,它會返回一個類型為T(返回它本身)COROUTINE_SUSPENDED標記聯合類型,因為Kotlin沒有聯合類型語法,所以就使用最泛化的類型Any?來表示,COROUTINE_SUSPENDED標記表示的是這個suspend函數會發(fā)生事實上的掛起操作。

在下面介紹的三個調度程序,它們都會繼承CoroutineDispatcher類,源碼如下所示:

// CorountineDispatcher.kt
public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {

    // 省略部分代碼

}

這個類實現了ContinuationInterceptor接口,源碼如下所示:

// ContinuationInterceptor.kt
@SinceKotlin("1.3")
public interface ContinuationInterceptor : CoroutineContext.Element {

    // 定義上下文攔截器的鍵
    companion object Key : CoroutineContext.Key<ContinuationInterceptor>

    // 返回原始封裝的Continuation,從而攔截所有的恢復
    public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>

    // 初始化時,為interceptContinuation返回的Continuation實例調用
    public fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        /* do nothing by default */
    }

    public override operator fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? {
        // getPolymorphicKey專門用于ContinuationInterceptor的鍵
        @OptIn(ExperimentalStdlibApi::class)
        if (key is AbstractCoroutineContextKey<*, *>) {
            @Suppress("UNCHECKED_CAST")
            return if (key.isSubKey(this.key)) key.tryCast(this) as? E else null
        }
        @Suppress("UNCHECKED_CAST")
        return if (ContinuationInterceptor === key) this as E else null
    }


    public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext {
        // minusPolymorphicKey專門用于ContinuationInterceptor的鍵
        @OptIn(ExperimentalStdlibApi::class)
        if (key is AbstractCoroutineContextKey<*, *>) {
            return if (key.isSubKey(this.key) && key.tryCast(this) != null) EmptyCoroutineContext else this
        }
        return if (ContinuationInterceptor === key) EmptyCoroutineContext else this
    }
}

這個接口叫做續(xù)體攔截器,它負責攔截協(xié)程在恢復后執(zhí)行的代碼(也就是續(xù)體),并且將其在指定線程或者線程池恢復。

編譯期間,每個suspend函數會被編譯成實現了Continuation接口的匿名類,其實調用suspend函數時,并不一定會掛起協(xié)程,舉個例子:有個網絡請求的邏輯調用了await函數,如果網絡請求還沒得到結果,那么協(xié)程就會被掛起,直到得到結果為止,續(xù)體攔截器只會攔截發(fā)生掛起后的掛起點的續(xù)體,而對于沒發(fā)生掛起的掛起點,協(xié)程會調用resumeWith函數,而不再需要續(xù)體攔截器處理。

續(xù)體攔截器會緩存攔截過的續(xù)體,并且在不需要它的時候調用releaseInterceptedContinuation函數釋放。

使用協(xié)程確保主線程安全

Kotlin協(xié)程使用調度程序來確定哪些線程用于執(zhí)行協(xié)程,所有協(xié)程都必須在調度程序中運行,協(xié)程可以可以暫停,而調度程序負責將其恢復。

Kotlin提供了三個調度程序,可以使用它們來指定應在何處運行協(xié)程

  • Dispatchers.Main:使用此調度程序可在Android主線程上運行協(xié)程,只能用于界面交互執(zhí)行快速工作,例如:調用suspend函數、運行Android界面框架操作更新LiveData對象
  • Dispatchers.Default:此調度程序適合在主線程之外執(zhí)行占用大量CPU資源的工作,例如:對列表排序解析JSON。
  • Dispatchers.IO:此調度程序適合在主線程之外執(zhí)行磁盤或者網絡I/O,例如:操作數據庫(使用Room)、向文件中寫入數據或者從文件中讀取數據運行任何網絡操作。

我們可以調用withContext函數,并且傳入相應的協(xié)程上下文(CoroutineContext)就可以調度線程

withContext函數是個suspend函數,它可以在不引用回調的情況下控制任何代碼行的線程池,因此可以將其應用于非常小函數,示例代碼如下所示:

suspend fun getUserInfo() {       // Dispatchers.Main
    val data = fetchUserInfo()    // Dispatchers.Main
    show(data)                    // Dispatchers.Main
}

suspend fun fetchUserInfo() {     // Dispatchers.Main
    withContext(Dispatchers.IO) { // Dispatchers.IO
        // 執(zhí)行網絡請求             // Dispatchers.IO
    }                             // Dispatchers.Main
}

在示例代碼中,getUserInfo函數主線程上執(zhí)行,它可以安全地調用fetchUserInfo函數,在工作線程中執(zhí)行網絡請求,并且掛起,在withContext代碼塊執(zhí)行完成后,主線程上的協(xié)程就會根據fetchUserInfo函數的結果恢復后面的邏輯。

相比于回調實現,使用withContext函數不會增加額外的開銷,在某些情況下,甚至優(yōu)于回調實現,例如:某個函數執(zhí)行了很多次網絡請求,使用外部withContext函數可以讓Kotlin停留在同一個調度程序,并且只切換一次線程,此外,Kotlin還優(yōu)化了Dispatchers.DefaultDispatchers.IO之間的切換,以盡可能避免線程切換。

要注意的是,Dispatchers.DefaultDispatchers.IO都是使用線程池調度程序,它們不能保證代碼塊在同一線程從上到下執(zhí)行,因為在某些情況下,Kotlin會在掛起恢復后,將執(zhí)行工作移交給另外一個線程,這意味著,對于整個withContext代碼塊,線程局部變量并不指向同一個值。

Dispatchers.Main

源碼如下所示:

// Dispatchers.kt
public actual object Dispatchers {
    // 省略部分代碼
    @JvmStatic
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
    // 省略部分代碼
}

然后看下MainDispatcherLoader.dispatcher,源碼如下所示:

// MainDispatchers.kt
internal object MainDispatcherLoader {

    // 省略部分代碼

    @JvmField
    val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()

    // 省略部分代碼

}

變量dispatcherMainCoroutineDispatcher類型,MainCoroutineDispatcher是個抽象類,然后它的其中一個實現類包裝類(sealed class)HandlerDispatcher,也就是它的子類肯定是在這個類的所在的文件中,然后我找到HandlerContext這個類,這個類是HandlerDispatcher唯一子類,源碼如下所示:

// MainCoroutineDispatcher.kt
internal class HandlerContext private constructor(
    private val handler: Handler,
    private val name: String?,
    private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
    // HandlerContext的構造函數,參數handler為要傳進來的Handler,參數name為用于調試的可選名稱
    public constructor(
        handler: Handler,
        name: String? = null
    ) : this(handler, name, false)

    @Volatile
    private var _immediate: HandlerContext? = if (invokeImmediately) this else null

    override val immediate: HandlerContext = _immediate ?:
        HandlerContext(handler, name, true).also { _immediate = it }

    // 判斷是否需要調度,參數context為CoroutineContext
    override fun isDispatchNeeded(context: CoroutineContext): Boolean {
        // 判斷invokeImmediately的值或者是否是同一個線程
        return !invokeImmediately || Looper.myLooper() != handler.looper
    }

    // 調度線程,參數context為CoroutineContext,參數block為Runnable
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        // 調用Handler的post方法,將Runnable添加到消息隊列中,這個Runnable會在這個Handler附加在線程上的時候運行
        handler.post(block)
    }

    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        val block = Runnable {
            with(continuation) { resumeUndispatched(Unit) }
        }
        // 調用Handler的postDelayed方法,將Runnable添加到消息隊列中,并且在指定的時間結束后運行
        handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
        continuation.invokeOnCancellation { handler.removeCallbacks(block) }
    }

    override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
        // 調用Handler的postDelayed方法,將Runnable添加到消息隊列中,并且在指定的時間結束后運行
        handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
        return object : DisposableHandle {
            override fun dispose() {
                // 調用Handler的removeCallbacks方法,刪除消息隊列中的Runnable
                handler.removeCallbacks(block)
            }
        }
    }

    override fun toString(): String =
        if (name != null) {
            if (invokeImmediately) "$name [immediate]" else name
        } else {
            handler.toString()
        }

    override fun equals(other: Any?): Boolean = other is HandlerContext && other.handler === handler
    override fun hashCode(): Int = System.identityHashCode(handler)
}

然后我們找下調用HandlerContext構造函數的地方,源碼如下所示:

// HandlerDispatcher.kt
@JvmField
@Deprecated("Use Dispatchers.Main instead", level = DeprecationLevel.HIDDEN)
internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main") }.getOrNull()

可以看到傳入了Looper.getMainLooper方法,也就是應用程序主循環(huán)程序(Main Looper),它位于應用程序主線程中。

可以看到使用了很多Handler相關的方法,也就是它還是依賴于Android消息機制

Dispatchers.Default

源碼如下所示:

// Dispatchers.kt
public actual object Dispatchers {

    // 省略部分代碼

    @JvmStatic
    public actual val Default: CoroutineDispatcher = createDefaultDispatcher()

    // 省略部分代碼

}

然后看下createDefaultDispatcher函數,源碼如下所示:

// CoroutineContext.kt
internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
    if (useCoroutinesScheduler) DefaultScheduler else CommonPool

這里會根據內部變量(internal val)useCoroutinesScheduler判斷返回是DefaultScheduler還是CommonPool,useCoroutinesScheduler源碼如下所示:

// CoroutineContext.kt
internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler"

internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value ->
    when (value) {
        null, "", "on" -> true
        "off" -> false
        else -> error("System property '$COROUTINES_SCHEDULER_PROPERTY_NAME' has unrecognized value '$value'")
    }
}

這個內部變量(internal val)useCoroutinesScheduler是根據JVMSystem.getProperty方法獲取的,通過傳入"kotlinx.coroutines.scheduler"作為鍵(key),返回的值為onuseCoroutinesSchedulertrue;返回的值是off,useCoroutinesSchedulerfalse

先看下DefaultScheduler這種情況,源碼如下所示:

// Dispatcher.kt
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
    val IO = blocking(systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)))

    override fun close() {
        throw UnsupportedOperationException("$DEFAULT_SCHEDULER_NAME cannot be closed")
    }

    override fun toString(): String = DEFAULT_SCHEDULER_NAME

    @InternalCoroutinesApi
    @Suppress("UNUSED")
    public fun toDebugString(): String = super.toString()
}

它繼承ExperimentalCoroutineDispatcher類,它是個不穩(wěn)定的類,以后可能會改變,可以看下這個類的dispatch函數,這個函數負責調度線程,源碼如下所示:

// Dispatcher.kt
@InternalCoroutinesApi
open class ExperimentalCoroutineDispatcher(
    // 核心線程數
    private val corePoolSize: Int,
    // 最大線程數
    private val maxPoolSize: Int,
    // 調度器保持存活的時間(單位:納秒)
    private val idleWorkerKeepAliveNs: Long,
    // 調度器名字
    private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
    constructor(
        corePoolSize: Int = CORE_POOL_SIZE,
        maxPoolSize: Int = MAX_POOL_SIZE,
        schedulerName: String = DEFAULT_SCHEDULER_NAME
    ) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)

    @Deprecated(message = "Binary compatibility for Ktor 1.0-beta", level = DeprecationLevel.HIDDEN)
    constructor(
        corePoolSize: Int = CORE_POOL_SIZE,
        maxPoolSize: Int = MAX_POOL_SIZE
    ) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS)

    // 省略部分代碼

    override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
        try {
            // 調用了coroutineScheduler的dispatch函數
            coroutineScheduler.dispatch(block)
        } catch (e: RejectedExecutionException) {
            DefaultExecutor.dispatch(context, block)
        }

    // 省略部分代碼

}

看下CoroutineScheduler這個類,然后再看下它的dispatch函數,源碼如下所示:

// CoroutineScheduler.kt
@Suppress("NOTHING_TO_INLINE")
internal class CoroutineScheduler(
    // 核心線程數
    @JvmField val corePoolSize: Int,
    // 最大線程數
    @JvmField val maxPoolSize: Int,
    // 調度器保持存活的時間(單位:納秒)
    @JvmField val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
    // 調度器名字
    @JvmField val schedulerName: String = DEFAULT_SCHEDULER_NAME
) : Executor, Closeable {
    init {
        require(corePoolSize >= MIN_SUPPORTED_POOL_SIZE) {
            "Core pool size $corePoolSize should be at least $MIN_SUPPORTED_POOL_SIZE"
        }
        require(maxPoolSize >= corePoolSize) {
            "Max pool size $maxPoolSize should be greater than or equals to core pool size $corePoolSize"
        }
        require(maxPoolSize <= MAX_SUPPORTED_POOL_SIZE) {
            "Max pool size $maxPoolSize should not exceed maximal supported number of threads $MAX_SUPPORTED_POOL_SIZE"
        }
        require(idleWorkerKeepAliveNs > 0) {
            "Idle worker keep alive time $idleWorkerKeepAliveNs must be positive"
        }
    }

   // 省略部分代碼

   fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
       // 用于支持虛擬時間
       trackTask()
       val task = createTask(block, taskContext)
       // 嘗試將任務提交到本地隊列,并且根據結果采取執(zhí)行相關的邏輯
       val currentWorker = currentWorker()
       val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
       if (notAdded != null) {
           if (!addToGlobalQueue(notAdded)) {
               // 全局隊列在最后一步關閉時不應該接受更多的任務
               throw RejectedExecutionException("$schedulerName was terminated")
           }
        }
        val skipUnpark = tailDispatch && currentWorker != null
        if (task.mode == TASK_NON_BLOCKING) {
            if (skipUnpark) return
            // 執(zhí)行任務
            signalCpuWork()
        } else {
            // 增加阻塞任務
            signalBlockingWork(skipUnpark = skipUnpark)
        }
   }

   // 省略部分代碼

}

可以看到CoroutineScheduler實現了Executor接口,在Java線程池核心實現類ThreadPoolExecutor類,它也是實現了Executor接口,所以這個CoroutineScheduler協(xié)程線程池的一種實現。

corePoolSize核心線程數量,它是通過調用JVMRuntime.getRuntime().availableProcessors()方法得到當前處理器可運行的線程數,它的缺省值強制設置為至少兩個線程。

maxPoolSize最大線程數量,最小值corePoolSize,最大值(1 shl BLOCKING_SHIFT) - 2BLOCKING_SHIFT21,也就是1向左位移21位再減去2,確保Runtime.getRuntime().availableProcessors()得到的值再乘以2最小值最大值之間。

這個函數做的事情就是將傳入的任務壓入任務棧,然后調用signalCpuWork執(zhí)行任務或者調用signalBlockingWork來增加阻塞任務。

然后再看下另外一種情況CommonPool,源碼如下所示:

// CommonPool.kt
internal object CommonPool : ExecutorCoroutineDispatcher() {

    // 省略部分代碼

    private fun createPool(): ExecutorService {
        if (System.getSecurityManager() != null) return createPlainPool()
        // ForkJoinPool類的反射,方便它在JDK6上可以運行(這里沒有),如果沒有就使用普通線程池
        val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") }
            ?: return createPlainPool()
        // 嘗試使用commonPool,除非顯式指定了并行性或者在調試privatePool模式
        if (!usePrivatePool && requestedParallelism < 0) {
            Try { fjpClass.getMethod("commonPool")?.invoke(null) as? ExecutorService }
                ?.takeIf { isGoodCommonPool(fjpClass, it) }
                ?.let { return it }
        }
        // 嘗試創(chuàng)建私有ForkJoinPool實例
        Try { fjpClass.getConstructor(Int::class.java).newInstance(parallelism) as? ExecutorService }
            ?. let { return it }
        // 使用普通線城市
        return createPlainPool()
    }

    // 省略部分代碼

    // 創(chuàng)建普通線程池
    private fun createPlainPool(): ExecutorService {
        val threadId = AtomicInteger()
        // 使用Java的newFixedThreadPool線程池
        return Executors.newFixedThreadPool(parallelism) {
            Thread(it, "CommonPool-worker-${threadId.incrementAndGet()}").apply { isDaemon = true }
        }
    }

    // 省略部分代碼

    // 調度線程
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        try {
            (pool ?: getOrCreatePoolSync()).execute(wrapTask(block))
        } catch (e: RejectedExecutionException) {
            unTrackTask()
            DefaultExecutor.enqueue(block)
        }
    }

    // 省略部分代碼

}

可以看到使用CommonPool,其實就是使用JavanewFixedThreadPool線程池

Dispatchers.Default調度器核心線程池處理器的線程數相等的,因此它可以用于處理密集型計算,適合在主線程之外執(zhí)行占用大量CPU資源的工作,例如:對列表排序解析JSON,和RxJava計算線程池的思想有點類似。

Dispatchers.IO

源碼如下所示:

// Dispatchers.kt
public actual object Dispatchers {

    // 省略部分代碼

    @JvmStatic
    public val IO: CoroutineDispatcher = DefaultScheduler.IO

}

可以看到IO其實是DefaultScheduler的一個成員變量,源碼如下所示:

internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
    // 調用了父類ExperimentalCoroutineDispatcher的blocking函數
    val IO = blocking(systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)))

    override fun close() {
        throw UnsupportedOperationException("$DEFAULT_SCHEDULER_NAME cannot be closed")
    }

    override fun toString(): String = DEFAULT_SCHEDULER_NAME

    @InternalCoroutinesApi
    @Suppress("UNUSED")
    public fun toDebugString(): String = super.toString()
}

可以看下它的父類ExperimentalCoroutineDispatcherblocking函數,源碼如下所示:

// Dispatcher.kt
@InternalCoroutinesApi
open class ExperimentalCoroutineDispatcher(
    // 核心線程數
    private val corePoolSize: Int,
    // 最大線程數
    private val maxPoolSize: Int,
    // 調度器保持存活的時間(單位:納秒)
    private val idleWorkerKeepAliveNs: Long,
    // 調度器名字
    private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
    constructor(
        corePoolSize: Int = CORE_POOL_SIZE,
        maxPoolSize: Int = MAX_POOL_SIZE,
        schedulerName: String = DEFAULT_SCHEDULER_NAME
    ) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)

    @Deprecated(message = "Binary compatibility for Ktor 1.0-beta", level = DeprecationLevel.HIDDEN)
    constructor(
        corePoolSize: Int = CORE_POOL_SIZE,
        maxPoolSize: Int = MAX_POOL_SIZE
    ) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS)

    // 省略部分代碼

    public fun blocking(parallelism: Int = BLOCKING_DEFAULT_PARALLELISM): CoroutineDispatcher {
        require(parallelism > 0) { "Expected positive parallelism level, but have $parallelism" }
        // 創(chuàng)建LimitingDispatcher對象
        return LimitingDispatcher(this, parallelism, TASK_PROBABLY_BLOCKING)
    }

    // 省略部分代碼

}

看下LimitingDispatcher類,源碼如下所示:

// Dispatcher.kt
private class LimitingDispatcher(
    // final變量dispatcher為ExperimentalCoroutineDispatcher類型
    val dispatcher: ExperimentalCoroutineDispatcher,
    val parallelism: Int,
    override val taskMode: Int
) : ExecutorCoroutineDispatcher(), TaskContext, Executor {

    // 省略部分代碼

    // 調度線程,調用dispatch(block: Runnable, tailDispatch: Boolean)函數
    override fun dispatch(context: CoroutineContext, block: Runnable) = dispatch(block, false)

    private fun dispatch(block: Runnable, tailDispatch: Boolean) {
        var taskToSchedule = block
        while (true) {
            // 提交正在執(zhí)行的任務槽
            val inFlight = inFlightTasks.incrementAndGet()

            // 快速路徑,如果沒有達到并行性限制,就會分派任務并且返回
            if (inFlight <= parallelism) {
                // 調用ExperimentalCoroutineDispatcher的dispatchWithContext函數
                dispatcher.dispatchWithContext(taskToSchedule, this, tailDispatch)
                return
            }

            // 達到并行性限制后就將任務添加到隊列中
            queue.add(taskToSchedule)

            if (inFlightTasks.decrementAndGet() >= parallelism) {
                return
            }

            taskToSchedule = queue.poll() ?: return
        }
    }

    // 省略部分代碼

}

可以看到其實Dispatchers.Default調度器Dispatchers.IO調度器共用同一個線程池的。

指定CoroutineScope

在定義協(xié)程時,必須指定其CoroutineScope,CoroutineScope可以管理一個或者多個相關的協(xié)程,可以使用它在指定范圍內啟動新協(xié)程。

與調度程序不同,CoroutineScope不運行協(xié)程。

CoroutineScope一項重要功能就是在用戶離開應用中內容區(qū)域時停止執(zhí)行協(xié)程,可以確保所有正在運行的操作都能正確停止

Android平臺上,可以將CoroutineScope實現與組件中生命周期相關聯,例如:LifecycleViewModel,這樣可以避免內存泄漏和不再對與用戶相關的Activity或者Fragment執(zhí)行額外的工作,例如:ViewModelScope、LifecycleScopeliveData。

添加KTX依賴項
  • 對于ViewModelScope,請使用androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-beta01或更高版本。
  • 對于LifecycleScope,請使用androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01或更高版本
  • 對于liveData,請使用androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01或更高版本。
生命周期感知型CoroutineScope
ViewModelScope

ViewModel定義ViewModelScope,如果ViewModel已清除,那么在這個范圍內的啟動的協(xié)程就會自動取消,如果你的工作需要在ViewModel處于活動狀態(tài)下才能完成的話,可以使用它,示例代碼如下所示:

class MyViewModel : ViewModel() {

    init {
        viewModelScope.launch {
            // 當ViewModel被清除,這個范圍內啟動的協(xié)程就會自動取消
        }
    }

}
LifecycleScope

為每個Lifecycle對象定義LifecycleScope,在這個范圍內啟動的協(xié)程會在Lifecycle被銷毀的時候自動取消,可以通過lifecycle.coroutineScope或者lifecycleOwner.lifecycleScope屬性訪問LifecycleCoroutineScope,示例代碼如下所示:

class MyFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            // 在這個范圍內啟動的協(xié)程會在Lifecycle被銷毀的時候自動取消
        }
    }
}

即使CoroutineScope提供了適當的方法來自動取消長時間運行的操作,在某些情況下,可能需要暫停執(zhí)行代碼塊,例如:要使用FragmentTransaction,Fragment的生命周期至少處于STARTED狀態(tài),對于這種情況,Lifecycle提供了這些方法:lifecycle.whenCreated、lifecycle.whenStartedlifecycle.whenResumed,如果Lifecycle未至少處于所需的最低狀態(tài),那么就會暫停在這些代碼塊內運行的任何協(xié)程,示例代碼如下所示:

class MyFragment : Fragment() {

    init {
        // 在Fragment的構造函數可以安全地啟動
        lifecycleScope.launch {
            whenCreateed {
                // 只有當Fragment的生命周期至少處于CREATED狀態(tài)下,這個代碼塊才會執(zhí)行,并且可以調用其他suspend函數
            }

            whenStarted {
                // 只有當Fragment的生命周期至少處于STARTED狀態(tài)下,這個代碼塊才會執(zhí)行,并且可以調用其他suspend函數
            }

            whenResumed {
                // 只有當Fragment的生命周期至少處于RESUMED狀態(tài)下,這個代碼塊才會執(zhí)行,并且可以調用其他suspend函數
            }
        }
    }

}
liveData

使用LiveData時,我們可能需要異步計算值,例如:獲取了用戶信息后顯示到界面,在這種情況下,我們可以使用liveData構建器函數調用suspend函數,并且將結果作為LiveData對象返回,示例代碼如下所示:

val userInfoData: LiveData<UserInfoData> = liveData {
    // getUserInfo函數是一個suspend函數
    val data = remoteSource.getUserInfo()
    emit(data)
}

liveData構建塊用作協(xié)程LiveData之間的結構化并發(fā)基元。

LiveData變?yōu)?strong>活動狀態(tài)的時候,代碼塊開始執(zhí)行;當LiveData變?yōu)?strong>非活動狀態(tài)的時候,代碼塊會在可配置的超時過后自動取消。如果代碼塊完成前取消,則會在LiveData再次變成活動狀態(tài)重啟;如果在上次運行中成功完成,則不會重啟。要注意的是,代碼塊只有在自動取消的情況下才會重啟,如果代碼塊由于任何其他原因(例如:拋出CancelationException)取消,則不會重啟。

我們可以從代碼塊中發(fā)出多個值,每次調用emit函數都會暫停執(zhí)行代碼塊,直到在主線程上設置LiveData值。

我的GitHub:TanJiaJunBeyond

Android通用框架:Android通用框架

我的掘金:譚嘉俊

我的簡書:譚嘉俊

我的CSDN:譚嘉俊

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。