Kotlin協(xié)程Context應用使用示例詳解
1.Context的應用
Context在啟動協(xié)程模式中就已經(jīng)遇到過叫CoroutineContext
,它的意思就是協(xié)程上下文,線程的切換離不開它。
在啟動協(xié)程模式中也說明過為什么不用傳遞Context,因為它有一個默認值EmptyCoroutineContext
,需要注意的是這個Context是不可以切換線程的因為它是一個空的上下文對象,如果有這個需求就需要傳入具體的Context,例如Dispatchers.IO
。
//launch public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine } //runBlocking public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T { } //async 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 }
當傳入Dispatchers.IO
時執(zhí)行的線程有什么變化呢?
fun main() = runBlocking { val user = contextTest() logX(user) } suspend fun contextTest(): String { logX("Start Context") withContext(Dispatchers.IO) { logX("Loading Context.") delay(1000L) } logX("After Context.") return "End Context" } fun logX(any: Any?) { println( """ ================================ $any Thread:${Thread.currentThread().name} ================================ """.trimIndent() ) } //輸出結(jié)果: //================================ //Start Context //Thread:main @coroutine#1 //================================ //================================ //Loading Context. //Thread:DefaultDispatcher-worker-1 @coroutine#1 //================================ //================================ //After Context. //Thread:main @coroutine#1 //================================ //================================ //End Context //Thread:main @coroutine#1 //================================
從輸出結(jié)果可以得出一個結(jié)論:默認是運行在main
線程中當傳入Dispatchers.IO
之后就會進入到IO
線程執(zhí)行,然后在IO
線程執(zhí)行完畢后又回到了main
線程,那么除了這兩個線程之外是否還有其他線程呢?答案是有,除了這兩個之外還有2個:
public actual object Dispatchers { /** * 用于CPU密集型任務的線程池,一般來說它內(nèi)部的線程個數(shù)是與機器 CPU 核心數(shù)量保持一致的 * 不過它有一個最小限制2, */ public actual val Default: CoroutineDispatcher = DefaultScheduler /** * 主線程,在Android中才可以使用,主要用于UI的繪制,在普通JVM上無法使用 */ public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher /** * 不局限于任何特定線程,會根據(jù)運行時的上下文環(huán)境決定 */ public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined /** * 用于執(zhí)行IO密集型任務的線程池,它的數(shù)量會多一些,默認最大線程數(shù)量為64個 * 具體的線程數(shù)量可以通過kotlinx.coroutines.io.parallelism配置 * 它會和Default共享線程,當Default還有其他空閑線程時是可以被IO線程池復用。 */ public val IO: CoroutineDispatcher = DefaultIoScheduler }
除了上述幾個Dispatcher
之外還可以自定義Dispatcher
fun main() = runBlocking { val user = contextTest() logX(user) } suspend fun contextTest(): String { logX("Start Context") //使用自定義的dispatcher // ↓ withContext(myDispatcher) { logX("Loading Context.") delay(100L) } logX("After Context.") return "End Context" } val myDispatcher = Executors.newSingleThreadExecutor { Thread(it, "myDispatcher").apply { isDaemon = true } }.asCoroutineDispatcher() //輸出結(jié)果 //================================ //Start Context //Thread:main @coroutine#1 //================================ //================================ //Loading Context. //Thread:myDispatcher @coroutine#1 //================================ //================================ //After Context. //Thread:main @coroutine#1 //================================ //================================ //End Context //Thread:main @coroutine#1 //================================
通過 asCoroutineDispatcher() 這個擴展函數(shù),創(chuàng)建了一個 Dispatcher。從這里也能看到,Dispatcher 的本質(zhì)仍然還是線程。那么可以得出一個結(jié)論:協(xié)程是運行在線程之上的。
前面還有一個線程Unconfined
,它是一個特殊的線程,沒有指定可運行在哪里,但是這個使用時需要謹慎甚至最好不用,通過下面的的代碼對比一下:
//不設(shè)置執(zhí)行線程 fun main() = runBlocking { logX("Start launch.") launch { logX("Start Delay launch.") delay(1000L) logX("End Delay launch.") } logX("End launch") } //輸出結(jié)果 //================================ //Start launch. //Thread:main @coroutine#1 //================================ //================================ //End launch //Thread:main @coroutine#1 //================================ //================================ //Start Delay launch. //Thread:main @coroutine#2 //================================ //================================ //End Delay launch. //Thread:main @coroutine#2 //================================ //設(shè)置執(zhí)行線程 fun main() = runBlocking { logX("Start launch.") // 變化在這里 // ↓ launch(Dispatchers.Unconfined) { logX("Start Delay launch.") delay(1000L) logX("End Delay launch.") } logX("End launch") } //輸出結(jié)果 //================================ //Start launch. //Thread:main @coroutine#1 //================================ //================================ //Start Delay launch. //Thread:main @coroutine#2 //================================ //================================ //End launch //Thread:main @coroutine#1 //================================ //================================ //End Delay launch. //Thread:kotlinx.coroutines.DefaultExecutor @coroutine#2 //================================
經(jīng)過對比可以發(fā)現(xiàn)加入Dispatchers.Unconfined
會導致代碼的運行順序被修改,這種錯誤的產(chǎn)生一定會對項目調(diào)試造成非常大的影響,而且Dispatchers.Unconfined
的定義初衷也不是為了修改代碼的執(zhí)行順序。
2.萬物皆有 Context
在Kotlin協(xié)程中,但凡是重要的概念都直接或間接的與CoroutineContext
有關(guān)系,例如Job
、Dispatcher
、CoroutineExceptionHandler
、CoroutineScope
等
1.CoroutineScope
public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine } /** * CoroutineScope的作用域就是把CoroutineContext做了一層封裝,核心實現(xiàn)均來自于CoroutineContext */ public interface CoroutineScope { /** * 此作用域的上下文。上下文被作用域封裝,并用于實現(xiàn)作為作用域擴展的協(xié)程構(gòu)建器 */ public val coroutineContext: CoroutineContext }
CoroutineScope
的源碼注釋寫的很清楚,核心實現(xiàn)在于CoroutineContext
,CoroutineScope
只是做了封裝而已,然后就可以批量的控制協(xié)程了,例如下面的代碼實現(xiàn):
fun main() = runBlocking { val scope = CoroutineScope(Job()) scope.launch { logX("launch 1") } scope.launch { logX("launch 2") } scope.launch { logX("launch 3") } scope.launch { logX("launch 4") } delay(500L) scope.cancel() delay(1000L) }
2.Job
//Job#Job public interface Job : CoroutineContext.Element { } //CoroutineContext#Element public interface CoroutineContext { /** * 從該上下文返回具有給定鍵的元素,或返回null */ public operator fun <E : Element> get(key: Key<E>): E? /** * 從初始值開始累積此上下文的條目,并從左到右對當前累加器值和此上下文的每個元素應用操作。 */ public fun <R> fold(initial: R, operation: (R, Element) -> R): R /** * 返回包含該上下文和其他上下文元素的上下文。 * 刪除這個上下文中與另一個上下文中具有相同鍵的元素。 */ public operator fun plus(context: CoroutineContext): CoroutineContext {} /** * 返回包含此上下文中的元素的上下文,但不包含具有指定鍵的元素。 */ public fun minusKey(key: Key<*>): CoroutineContext /** * CoroutineContext元素的鍵 */ public interface Key<E : Element> /** * CoroutineContext的一個元素。協(xié)程上下文的一個元素本身就是一個單例上下文。 */ public interface Element : CoroutineContext { } }
Job
實現(xiàn)了CoroutineContext.Element
,CoroutineContext.Element
又實現(xiàn)了CoroutineContext
那么就可以認為Job
間接實現(xiàn)了CoroutineContext
,所以可以認定Job
就是一個CoroutineContext
。
所以在定義Job
時下面兩種定義方式都可以:
val job: CoroutineContext = Job() val job: Job = Job()
3.Dispatcher
public actual object Dispatchers { public actual val Default: CoroutineDispatcher = DefaultScheduler public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined public val IO: CoroutineDispatcher = DefaultIoScheduler public fun shutdown() { } } public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {} public interface ContinuationInterceptor : CoroutineContext.Element {
Dispatcher
中的每一個線程繼承自CoroutineDispatcher
,CoroutineDispatcher
實現(xiàn)了ContinuationInterceptor
接口,ContinuationInterceptor
又實現(xiàn)了CoroutineContext
接口,由此就可以知道Dispatcher
和CoroutineContext
是如何產(chǎn)生關(guān)聯(lián)的了,或者說Dispatcher
就是CortinueContext
。
4.CoroutineExceptionHandler
/** * 協(xié)程上下文中一個可選的元素,用于處理未捕獲的異常 */ public interface CoroutineExceptionHandler : CoroutineContext.Element { /** * */ public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler> /** * 處理給定上下文中未捕獲的異常。如果協(xié)程有未捕獲的異常,則調(diào)用它。 */ public fun handleException(context: CoroutineContext, exception: Throwable) }
CoroutineExceptionHandler
主要用來處理協(xié)程中未捕獲的異常,未捕獲的異常只能來自根協(xié)程,子協(xié)程未捕獲的異常會委托給它們的父協(xié)程,父協(xié)程也委托給父協(xié)程,以此類推,直到根協(xié)程。所以安裝在它們上下文中的CoroutineExceptionHandler
永遠不會被使用。
以上就是Kotlin協(xié)程Context應用使用示例詳解的詳細內(nèi)容,更多關(guān)于Kotlin協(xié)程Context應用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)之Android studio的安裝與使用
本文是此系列文章的第一篇,主要給大家講述的是Android studio的安裝與使用,十分的詳細,有需要的小伙伴可以參考下2016-02-02新版Android studio導入微信支付和支付寶官方Demo問題解決大全
這篇文章主要為大家詳細介紹了新版Android studio導入微信支付和支付寶官方Demo問題的解決大全,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-07-07Android 程序執(zhí)行Linux命令的解決方法及注意事項
這篇文章主要介紹了Android 程序執(zhí)行LINUX命令的解決方法及注意事項,本文通過問題描述最終到解決方法,給大家介紹的非常詳細,需要的朋友可以參考下2017-12-12android studio與手機連接調(diào)試步驟詳解
這篇文章主要為大家詳細介紹了android studio與手機連接調(diào)試步驟,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07android仿微信通訊錄搜索示例(匹配拼音,字母,索引位置)
本篇文章主要介紹了android仿微信通訊錄搜索示例(匹配拼音,字母,索引位置),具有一定的參考價值,有興趣的可以了解一下2017-09-09Android調(diào)用系統(tǒng)照相機拍照與攝像的方法
這篇文章主要為大家詳細介紹了Android如何調(diào)用系統(tǒng)現(xiàn)有的照相機拍照與攝像,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-04-04Android?Flutter實現(xiàn)任意拖動的控件
使用flutter開發(fā)是需要控件能拖動,比如畫板中的元素,或者工具條等,所以本文為大家準備了Flutter實現(xiàn)任意拖動控件的示例代碼,希望對大家有所幫助2023-07-07