協(xié)程作用域CoroutineScope
在 Android 環(huán)境中,通常每個(gè)界面(Activity、Fragment 等)啟動(dòng)的 Coroutine 只在該界面有意義,如果用戶在等待 Coroutine 執(zhí)行的時(shí)候退出了這個(gè)界面,則再繼續(xù)執(zhí)行這個(gè) Coroutine 可能是沒(méi)必要的。另外 Coroutine 也需要在適當(dāng)?shù)?context 中執(zhí)行,否則會(huì)出現(xiàn)錯(cuò)誤,比如在非 UI 線程去訪問(wèn) View。 所以 Coroutine 在設(shè)計(jì)的時(shí)候,要求在一個(gè)范圍(Scope)內(nèi)執(zhí)行,這樣當(dāng)這個(gè) Scope 取消的時(shí)候,里面所有的子 Coroutine 也自動(dòng)取消。所以要使用 Coroutine 必須要先創(chuàng)建一個(gè)對(duì)應(yīng)的 CoroutineScope
。
CoroutineScope 接口
CoroutineScope 是一個(gè)接口,要是查看這個(gè)接口的源代碼的話就發(fā)現(xiàn)這個(gè)接口里面只定義了一個(gè)屬性 CoroutineContext
:
public interface CoroutineScope {
// Scope 的 Context
public val coroutineContext: CoroutineContext
}
所以 CoroutineScope 只是定義了一個(gè)新 Coroutine 的執(zhí)行 Scope。每個(gè)協(xié)程coroutine builder(launch 、async等) 都是 CoroutineScope 的擴(kuò)展方法,并且自動(dòng)的繼承了當(dāng)前 Scope 的 coroutineContext 和取消操作。
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
......
}
每個(gè) coroutine builder 和 scope 方法(withContext、coroutineScope 等)都使用自己的 Scope 和 自己管理的 Job 來(lái)運(yùn)行提供給這些函數(shù)的代碼塊。并且也會(huì)等待該代碼塊中所有子 Coroutine 執(zhí)行,當(dāng)所有子 Coroutine 執(zhí)行完畢并且返回的時(shí)候, 該代碼塊才執(zhí)行完畢,這種行為被稱之為 “structured concurrency”。
全局作用域GlobalScope
GlobalScope 是 CoroutineScope 的一個(gè)單例實(shí)現(xiàn),其代碼也是非常簡(jiǎn)單的:
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
用法
GlobalScope.launch(Dispatchers.Main) {
delay(7000)
val result = "content"
//主線程里更新 UI
text.text = result
}
該實(shí)例所用的 CoroutineContext 是一個(gè) EmptyCoroutineContext 實(shí)例(這也是一個(gè)單例 object 對(duì)象)。由于 GlobalScope 對(duì)象沒(méi)有和應(yīng)用生命周期組件相關(guān)聯(lián),需要自己管理 GlobalScope 所創(chuàng)建的 Coroutine,所以一般而言我們不直接使用 GlobalScope 來(lái)創(chuàng)建 Coroutine。
與生命周期綁定的作用域
一般而言,在應(yīng)用中具有生命周期的組件應(yīng)該實(shí)現(xiàn) CoroutineScope 接口,并負(fù)責(zé)該組件內(nèi) Coroutine 的創(chuàng)建和管理。例如對(duì)于 Android 應(yīng)用來(lái)說(shuō),可以在 Activity 中實(shí)現(xiàn) CoroutineScope 接口, 例如:
class ScopedActivity : Activity(), CoroutineScope {
lateinit var job: Job
// CoroutineScope 的實(shí)現(xiàn)
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
/*
* 注意 coroutine builder 的 scope, 如果 activity 被銷毀了或者該函數(shù)內(nèi)創(chuàng)建的 Coroutine
* 拋出異常了,則所有子 Coroutines 都會(huì)被自動(dòng)取消。不需要手工去取消。
*/
launch { // <- 自動(dòng)繼承當(dāng)前 activity 的 scope context,所以在 UI 線程執(zhí)行
val ioData = async(Dispatchers.IO) { // <- launch scope 的擴(kuò)展函數(shù),指定了 IO dispatcher,所以在 IO 線程運(yùn)行
// 在這里執(zhí)行阻塞的 I/O 耗時(shí)操作
}
// 和上面的并非 I/O 同時(shí)執(zhí)行的其他操作
val data = ioData.await() // 等待阻塞 I/O 操作的返回結(jié)果
draw(data) // 在 UI 線程顯示執(zhí)行的結(jié)果
}
}
override fun onDestroy() {
super.onDestroy()
// 當(dāng) Activity 銷毀的時(shí)候取消該 Scope 管理的 job。
// 這樣在該 Scope 內(nèi)創(chuàng)建的子 Coroutine 都會(huì)被自動(dòng)的取消。
job.cancel()
}
}
由于所有的 Coroutine 都需要一個(gè) CoroutineScope,所以為了方便創(chuàng)建 Coroutine,在 CoroutineScope 上有很多擴(kuò)展函數(shù),比如 launch、async、actor、cancel 等。
MainScope
在 Android 中會(huì)經(jīng)常需要實(shí)現(xiàn)這個(gè) CoroutineScope,所以為了方便開(kāi)發(fā)者使用, 標(biāo)準(zhǔn)庫(kù)中定義了一個(gè) MainScope() 函數(shù),該函數(shù)定義了一個(gè)使用 SupervisorJob 和 Dispatchers.Main 為 Scope context 的實(shí)現(xiàn)。所以上面的代碼可以簡(jiǎn)化為:
class ScopedActivity : Activity(),
CoroutineScope by MainScope(){ // 使用 by 指定代理實(shí)現(xiàn)
override fun onDestroy() {
super.onDestroy()
cancel() // 調(diào)用 CoroutineScope 的 cancel 函數(shù)
}
在mvvm模式使用作用域
class ViewModelOne : ViewModel() {
private val viewModelJob = SupervisorJob()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
val mMessage: MutableLiveData<String> = MutableLiveData()
fun getMessage(message: String) {
uiScope.launch {
val deferred = async(Dispatchers.IO) {
delay(2000)
"post $message"
}
mMessage.value = deferred.await()
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
ViewModelScope 方式
AndroidX Lifecycle v2.1.0 在 ViewModel 中引入 viewModelScope,當(dāng) ViewModel 被銷毀時(shí)它會(huì)自動(dòng)取消協(xié)程任務(wù),這個(gè)特性真的好用。viewModelScope 管理協(xié)程的方式與我們?cè)?ViewModel 引入?yún)f(xié)程的方式一樣,代碼實(shí)現(xiàn)如下:
class MyViewModel : ViewModel() {
fun launchDataLoad() {
viewModelScope.launch {
sortList()
// Modify UI
}
}
suspend fun sortList() = withContext(Dispatchers.Default) {
// Heavy work
}
}
協(xié)程上下文CoroutineContext
CoroutineContext是一個(gè)接口,我們常用到的Job, Dispatchers都是實(shí)現(xiàn)了該接口的類,此外還包括 CoroutineName 和CoroutineId等類。
@SinceKotlin("1.3")
public interface CoroutineContext {
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
public interface Key<E : Element>
public interface Element : CoroutineContext {
public val key: Key<*>
...
}
}
類似于一個(gè)以 Key 為索引的 List:
CoroutineContext 作為一個(gè)集合,它的元素就是源碼中看到的 Element,每一個(gè) Element 都有一個(gè) key,因此它可以作為元素出現(xiàn),同時(shí)它也是 CoroutineContext 的子接口,因此也可以作為集合出現(xiàn)。
我們看下實(shí)現(xiàn)了上下文接口的Job類
public interface Job : CoroutineContext.Element {
/**
* Key for [Job] instance in the coroutine context.
*/
public companion object Key : CoroutineContext.Key<Job> {
init {
/*
* Here we make sure that CoroutineExceptionHandler is always initialized in advance, so
* that if a coroutine fails due to StackOverflowError we don't fail to report this error
* trying to initialize CoroutineExceptionHandler
*/
CoroutineExceptionHandler
}
}
}
可以看到Job實(shí)現(xiàn)關(guān)系是Job<=Element<=CoroutineContext
public interface Element : CoroutineContext {
/**
* A key of this coroutine context element.
*/
public val key: Key<*>
public override operator fun <E : Element> get(key: Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
協(xié)程生成器 Coroutine builders
我們知道生成協(xié)程的方式有很多種,比如 launch、async、runBlocking等,他們都是
CoroutineScope的擴(kuò)展方法,且都會(huì)創(chuàng)建一個(gè)新的協(xié)程
下面我們拿launch開(kāi)啟一個(gè)協(xié)程的例子來(lái)了解下如何設(shè)置上下文
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
...
}
我們?yōu)樯舷挛奶砑诱{(diào)度器與上下文名字
launch(context = Dispatchers.Main.plus(CoroutineName("jason"))) {
val job = coroutineContext.get(Job)
val dispatcher = coroutineContext.get(ContinuationInterceptor)
val name = coroutineContext.get(CoroutineName)
println("job:$job")
println("dispatcher:$dispatcher")
println("name:$name")
}
打印輸出結(jié)果
job:StandaloneCoroutine{Active}@ad739cb
dispatcher:Main
name:CoroutineName(jason)
上下文切換器withContext
與 launch、async、runBlocking 等不同,withContext 不會(huì)創(chuàng)建新的協(xié)程。
withContext 允許切換協(xié)程上下文,使用時(shí)必須傳遞一個(gè) CoroutineContext
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
...
}
看下例子
launch(context = Dispatchers.IO.plus(CoroutineName("c1"))) {
val dispatcher = coroutineContext[ContinuationInterceptor]
val name = coroutineContext[CoroutineName]
println("scope:$this")
println("dispatcher:$dispatcher")
println("name:$name")
withContext(Dispatchers.Main.plus(CoroutineName("c2"))){
val dispatcher2 = coroutineContext[ContinuationInterceptor]
val name2 = coroutineContext[CoroutineName]
println("scope2:$this")
println("dispatcher2:$dispatcher2")
println("name2:$name2")
}
}
日志輸出結(jié)果
scope:StandaloneCoroutine{Active}@60d84a6
dispatcher:LimitingDispatcher@903ee7[dispatcher = DefaultDispatcher]
name:CoroutineName(c1)
scope2:DispatchedCoroutine{Active}@e3f5a94
dispatcher2:Main
name2:CoroutineName(c2)
續(xù)體攔截器ContinuationInterceptor
攔截器也是上下文的一種,它實(shí)現(xiàn)了上下文接口
我們可以用攔截器打日志等。調(diào)度器就是基于攔截器實(shí)現(xiàn)的,換句話說(shuō)調(diào)度器就是攔截器的一種。
public interface ContinuationInterceptor : CoroutineContext.Element {
companion object Key : CoroutineContext.Key<ContinuationInterceptor>
public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
...
}
下面我們自己定義一個(gè)攔截器放到我們的協(xié)程上下文中,看看會(huì)發(fā)生什么。
class MyContinuationInterceptor: ContinuationInterceptor {
override val key = ContinuationInterceptor
override fun <T> interceptContinuation(continuation: Continuation<T>) = MyContinuation(continuation)
}
class MyContinuation<T>(val continuation: Continuation<T>): Continuation<T> {
override val context = continuation.context
override fun resumeWith(result: Result<T>) {
Log.d("jason","result=$result" )
continuation.resumeWith(result)
}
}
launch {
launch(MyContinuationInterceptor()) {
log(1)
val deferred = async {
log(2)
delay(1000)
log(3)
"我是返回值"
}
log(4)
val result = deferred.await()
log("5. $result")
}.join()
log(6)
}
我們通過(guò) launch 啟動(dòng)了一個(gè)協(xié)程,為它指定了我們自己的攔截器作為上下文,緊接著在其中用 async 啟動(dòng)了一個(gè)協(xié)程,async 與 launch 從功能上是同等類型的函數(shù),它們都被稱作協(xié)程的 Builder 函數(shù),不同之處在于 async 啟動(dòng)的 Job 也就是實(shí)際上的 Deferred 可以有返回結(jié)果,可以通過(guò) await 方法獲取。
輸出日志
23:02:58.595 5241-5241/com.example.mytest D/jason: result=Success(kotlin.Unit) //1
23:02:58.596 5241-5241/com.example.mytest D/jason: 1
23:02:58.598 5241-5241/com.example.mytest D/jason: result=Success(kotlin.Unit) //2
23:02:58.598 5241-5241/com.example.mytest D/jason: 2
23:02:58.602 5241-5241/com.example.mytest D/jason: 4
23:02:59.602 5241-5273/com.example.mytest D/jason: result=Success(kotlin.Unit) //3
23:02:59.602 5241-5273/com.example.mytest D/jason: 3
23:02:59.603 5241-5273/com.example.mytest D/jason: result=Success(我是返回值) //4
23:02:59.604 5241-5273/com.example.mytest D/jason: 5. 我是返回值
23:02:59.605 5241-5241/com.example.mytest D/jason: 6
首先,這段代碼中一共有4次調(diào)度機(jī)會(huì),所有協(xié)程啟動(dòng)的時(shí)候,都會(huì)有一次 Continuation.resumeWith 的操作,這一次操作對(duì)于調(diào)度器來(lái)說(shuō)就是一次調(diào)度的機(jī)會(huì),我們的協(xié)程有機(jī)會(huì)調(diào)度到其他線程的關(guān)鍵之處就在于此。 ①、② 兩處都是這種情況。
其次,delay 是掛起點(diǎn),1000ms 之后需要繼續(xù)調(diào)度執(zhí)行該協(xié)程,因此就有了 ③ 處的日志。
最后,④ 處的日志就很容易理解了,正是我們的返回結(jié)果。
如果我們?cè)跀r截器當(dāng)中自己處理了線程切換,那么就實(shí)現(xiàn)了自己的一個(gè)簡(jiǎn)單的調(diào)度器,大家有興趣可以自己去嘗試。
協(xié)程任務(wù)執(zhí)行環(huán)境-Dispatcher(調(diào)度器)
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
...
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
...
}
它本身是協(xié)程上下文的子類,同時(shí)實(shí)現(xiàn)了攔截器的接口, dispatch 方法會(huì)在攔截器的方法 interceptContinuation 中調(diào)用,進(jìn)而實(shí)現(xiàn)協(xié)程的調(diào)度。所以如果我們想要實(shí)現(xiàn)自己的調(diào)度器,繼承這個(gè)類就可以了,不過(guò)通常我們都用現(xiàn)成的
現(xiàn)成的調(diào)度器有:
調(diào)度器名稱 | 使用線程 |
---|---|
Main | UI線程 |
IO | 線程池 |
Default | 線程池 |
Unconfined | 直接執(zhí)行 |
也能使用這種方式簡(jiǎn)單的創(chuàng)建一個(gè)自定義的協(xié)程調(diào)度器
val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher()
這里我們主要看一下我們最常使用的Dispatcher.Main和Dispatcher.IO兩個(gè)派發(fā)器。
Dispatcher.Main
Dispatcher.Main沒(méi)有默認(rèn)實(shí)現(xiàn),依賴于各個(gè)平臺(tái)的實(shí)現(xiàn),如果沒(méi)有引入android依賴包,則會(huì)拋異常提示,那么kotlin是怎么支持這種動(dòng)態(tài)的類呢?
- 首先kotlin提供了一個(gè)工廠類接口,用來(lái)創(chuàng)建MainDispatcher
public interface MainDispatcherFactory {
fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher
}
- 然后再看反編譯的源碼
public final <S> List<S> loadProviders$kotlinx_coroutines_core(@NotNull Class<S> paramClass, @NotNull ClassLoader paramClassLoader) {
//從apk的META-INF/services/文件夾下那類名
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("META-INF/services/");
stringBuilder.append(paramClass.getName());
Enumeration enumeration = paramClassLoader.getResources(stringBuilder.toString());
ArrayList arrayList = Collections.list(enumeration);
Iterable iterable = (Iterable)arrayList;
Collection collection = (Collection)new ArrayList();
for (URL uRL : iterable) {
FastServiceLoader fastServiceLoader = INSTANCE;
Intrinsics.checkExpressionValueIsNotNull(uRL, "it");
CollectionsKt.addAll(collection, (Iterable)fastServiceLoader.parse(uRL));
}
collection = CollectionsKt.toSet((Iterable)collection);
iterable = (Iterable)collection;
collection = (Collection)new ArrayList(CollectionsKt.collectionSizeOrDefault(iterable, 10));
//將類名解析為實(shí)例對(duì)象
for (String str : iterable)
collection.add(INSTANCE.getProviderInstance(str, paramClassLoader, paramClass));
return (List)collection;
}
MainDispatcher的factory會(huì)從apk的META-INF/services/文件夾下獲取。
-
再看編譯生成的apk文件的該文件夾內(nèi)容
image.png
所以android的依賴包是通過(guò)向該文件注冊(cè)類名實(shí)現(xiàn)的注冊(cè)類,并且factory類為AndroidDispatcherFactory。
- 最后我們?cè)賮?lái)看下AndroidDispatcherFactory類
internal class AndroidDispatcherFactory : MainDispatcherFactory {
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) = HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main")
}
internal class HandlerContext private constructor(
private val handler: Handler,
private val name: String?,
private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
public constructor(
handler: Handler,
name: String? = null
) : this(handler, name, false)
//android中需要向主looper進(jìn)行提交調(diào)度
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return !invokeImmediately || Looper.myLooper() != handler.looper
}
//通過(guò)持有主線程looper的handler進(jìn)行調(diào)度
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
...
}
很清楚,就是用持有主線程looper的handler進(jìn)行任務(wù)的調(diào)度,確保任務(wù)會(huì)在主線程執(zhí)行。
Dispatcher.IO
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
val IO = blocking(systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)))
public fun blocking(parallelism: Int = BLOCKING_DEFAULT_PARALLELISM): CoroutineDispatcher {
return LimitingDispatcher(this, parallelism, TaskMode.PROBABLY_BLOCKING)
}
}
Dispatcher.IO是一個(gè)LimitingDispatcher實(shí)例,他可以控制同時(shí)并發(fā)任務(wù)數(shù),默認(rèn)為64個(gè),即最多有64個(gè)任務(wù)同時(shí)在運(yùn)行。
private class LimitingDispatcher(
val dispatcher: ExperimentalCoroutineDispatcher,
val parallelism: Int,
override val taskMode: TaskMode
) : ExecutorCoroutineDispatcher()
而LimitingDispatcher內(nèi)部真正調(diào)度任務(wù)的dispatcher是一個(gè)ExperimentalCoroutineDispatcher對(duì)象。
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)
private var coroutineScheduler = createScheduler()
override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
try {
coroutineScheduler.dispatch(block)
} catch (e: RejectedExecutionException) {
DefaultExecutor.dispatch(context, block)
}
private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
}
我們看到,該dispatcher里面的真正的線程池,是CoroutineScheduler對(duì)象,而核心線程數(shù)和最大線程數(shù),取決于可用CPU的數(shù)量。
internal val CORE_POOL_SIZE = systemProp(
"kotlinx.coroutines.scheduler.core.pool.size",
AVAILABLE_PROCESSORS.coerceAtLeast(2), // !!! at least two here
minValue = CoroutineScheduler.MIN_SUPPORTED_POOL_SIZE
)
internal val AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors()
協(xié)程調(diào)度器-CoroutineScheduler
這里我們挑幾個(gè)小細(xì)節(jié)看一下CoroutineScheduler是如何來(lái)優(yōu)化對(duì)線程的使用的。
i. 盡量使用當(dāng)前線程
private fun submitToLocalQueue(task: Task, fair: Boolean): Int {
val worker = currentWorker() ?: return NOT_ADDED
...
worker.localQueue.add(task, globalQueue)
...
}
private fun currentWorker(): Worker? = (Thread.currentThread() as? Worker)?.takeIf { it.scheduler == this }
如果當(dāng)前線程是Dispatcher.IO開(kāi)啟的工作線程,那么任務(wù)優(yōu)先交由該線程的任務(wù)隊(duì)列,等待處理。
ii. 雙重隊(duì)列
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, fair: Boolean = false) {
...
when (submitToLocalQueue(task, fair)) {
ADDED -> return
NOT_ADDED -> {
//本地隊(duì)列已滿,放入全局隊(duì)列,所有線程可取
globalQueue.addLast(task)
}
else -> requestCpuWorker() // ask for help
}
}
如果工作線程本地隊(duì)列無(wú)限大,一味的放入本地隊(duì)列的話,可能會(huì)造成單一線程工作,效率極低,于是每個(gè)工作線程有固定大小的queue,滿了之后,會(huì)放到全局queue中,等待任意空閑工作線程執(zhí)行。
iii.搶占其他線程的任務(wù)
//工作線程Worker類
override fun run() {
while (!isTerminated && state != WorkerState.TERMINATED) {
val task = findTask()
...
}
...
}
private fun findTaskWithCpuPermit(): Task? {
...
//從本地queue獲取任務(wù)
localQueue.poll()?.let { return it }
//從全局queue獲取任務(wù)
if (!globalFirst) globalQueue.removeFirstOrNull()?.let { return it }
//搶占其他線程任務(wù)
return trySteal()
}
private fun trySteal(): Task? {
...
//隨機(jī)一個(gè)工作線程
if (stealIndex == 0) stealIndex = nextInt(created)
...
val worker = workers[stealIndex]
if (worker !== null && worker !== this) {
//將其queue里的任務(wù)放到自己queue中
if (localQueue.trySteal(worker.localQueue, globalQueue)) {
return localQueue.poll()
}
}
return null
}
如果一個(gè)工作線程的本地queue和全局queue都沒(méi)有任務(wù)了,但是其他線程的queue還有任務(wù),此時(shí)讓其空閑,一是沒(méi)有充分利用線程提升工作效率,二是線程的空閑狀態(tài)切換需要開(kāi)銷,所以此時(shí)會(huì)嘗試從任一工作線程的queue中取出任務(wù),放入自己的queue中執(zhí)行。
以上三點(diǎn)的相互配合,可以充分利用線程資源,避免過(guò)多線程的使用及開(kāi)銷,也保證了多任務(wù)時(shí)的工作效率。
協(xié)程執(zhí)行過(guò)程源碼追蹤分析
我們以一個(gè)請(qǐng)求數(shù)據(jù)后在主線程更新界面的代碼來(lái)進(jìn)行分析
fun setUpUI(){
GlobalScope.launch(Main) {
val dataDeferred = requestDataAsync()
doSomethingElse()
val data = dataDeferred.await()
processData(data)
}
Thread.sleep(1000)
doSomethingElse2()
}
fun requestDataAsync():Deferred<String>{
// 啟動(dòng)一個(gè)異步協(xié)程去執(zhí)行耗時(shí)任務(wù)
return GlobalScope.async {
requestData()
}
}
fun doSomethingElse2(){
println("doSomethingElse2")
}
編譯后生成偽代碼
final void setUpUI() {
BuildersKt__Builders_commonKt.launch$default(GlobalScope.INSTANCE,
Dispatchers.getMain(),
null,
// 傳入的是一個(gè) KotlinTest$setUpUI.KotlinTest$setUpUI$1 對(duì)象
(Function2)new KotlinTest$setUpUI.KotlinTest$setUpUI$1(this, (Continuation)null), 2, null);
this.doSomethingElse2();
}
final class setUpUI$1 extends SuspendLambda implements Function2{
public final Object invokeSuspend(Object result) {
switch (this.label) {
case 0:
doSomethingElse()
// 新建并啟動(dòng) async 協(xié)程
Deferred async$default = BuildersKt.async$default(coroutineScope, (CoroutineContext) Dispatchers.getDefault(), null, (Function2) new 1(null), 2, null);
this.label = 1;
// 如果 async 協(xié)程還沒(méi)完成為掛起狀態(tài) 則直接返回,等待下次喚醒重入
if (async$default.await(this) == coroutine_suspended) {
return coroutine_suspended;
}
break;
case 1:
val data = result;
processData(data)
break;
}
}
}
可以看到傳入到 launch 函數(shù)第四個(gè)參數(shù)位置的是一個(gè)編譯后生成的 SuspendLambda 類實(shí)例setUpUI$1,SuspendLambda 本質(zhì)上是一個(gè)續(xù)體 Continuation,而 Continuation 是一個(gè)有著恢復(fù)操作的接口
/**
* 在一個(gè)掛起點(diǎn)之后可以返回類型T值的續(xù)集continuation的接口
* Interface representing a continuation after a suspension point that returns value of type `T`.
*/
@SinceKotlin("1.3")
public interface Continuation<in T> {
/**
* Context of the coroutine that corresponds to this continuation.
*/
// todo: shall we provide default impl with EmptyCoroutineContext?
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
SuspendLambda 繼承結(jié)構(gòu)如下
SuspendLambda > ContinuationImpl > BaseContinuationImpl > Continuation
每一層封裝對(duì)應(yīng)添加了不同的功能,我們先忽略掉這些功能細(xì)節(jié),著眼于我們的主線,繼續(xù)跟進(jìn)launch 函數(shù)執(zhí)行過(guò)程,由于第二個(gè)參數(shù)是默認(rèn)值,所以創(chuàng)建的是 StandaloneCoroutine, 調(diào)用鏈如下:
coroutine.start(start, coroutine, block)
-> CoroutineStart.start(block, receiver, this)
-> CoroutineStart.invoke(block: suspend () -> T, completion: Continuation<T>)
-> block.startCoroutineCancellable(completion)
-> createCoroutineUnintercepted(completion).intercepted().resumeCancellable(Unit)
我們看最后創(chuàng)建了一個(gè)協(xié)程,并鏈?zhǔn)秸{(diào)用 intercepted、resumeCancellable 方法,利用協(xié)程上下文中的續(xù)體攔截器 ContinuationInterceptor 對(duì)協(xié)程的執(zhí)行進(jìn)行攔截,intercepted 實(shí)際上調(diào)用的是 ContinuationImpl 的 intercepted 方法
internal abstract class ContinuationImpl(
completion: Continuation<Any?>?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
...
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
...
}
context[ContinuationInterceptor]?.interceptContinuation調(diào)用的是 CoroutineDispatcher 的 interceptContinuation 方法
public final <T> Continuation<T> interceptContinuation(@NotNull final Continuation<? super T> continuation) {
Intrinsics.checkParameterIsNotNull(continuation, "continuation");
return new DispatchedContinuation<T>(this, continuation);
}
最終創(chuàng)建了一個(gè) DispatchedContinuation 可分發(fā)的協(xié)程實(shí)例,我們繼續(xù)看resumeCancellable 方法
internal fun <T> Continuation<T>.resumeCancellable(value: T) = when (this) {
// 判斷是否是DispatchedContinuation 根據(jù)我們前面的代碼追蹤 這里是DispatchedContinuation
is DispatchedContinuation -> resumeCancellable(value)
else -> resume(value)
}
inline fun resumeCancellable(value: T) {
// 判斷是否需要線程調(diào)度
if (dispatcher.isDispatchNeeded(context)) {
_state = value
resumeMode = MODE_CANCELLABLE
dispatcher.dispatch(context, this)
} else {
UndispatchedEventLoop.execute(this, value, MODE_CANCELLABLE) {
if (!resumeCancelled()) {
resumeUndispatched(value)
}
}
}
}
最終走到 dispatcher.dispatch(context, this) 而這里的 dispatcher 就是通過(guò)工廠方法創(chuàng)建的 HandlerDispatcher ,dispatch() 函數(shù)第二個(gè)參數(shù)this是一個(gè)runnable這里為 DispatchedTask
HandlerDispatcher
/**
* Implements [CoroutineDispatcher] on top of an arbitrary Android [Handler].
*/
internal class HandlerContext private constructor(
private val handler: Handler,
private val name: String?,
private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
...
// 最終執(zhí)行這里的 dispatch方法 而handler則是android中的 MainHandler
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
...
}
這里借用 Android 的主線程消息隊(duì)列來(lái)在主線程中執(zhí)行 block Runnable 而這個(gè) Runnable 即為 DispatchedTask
internal abstract class DispatchedTask<in T>(
@JvmField var resumeMode: Int
) : SchedulerTask() {
...
public final override fun run() {
...
withCoroutineContext(context, delegate.countOrElement) {
if (job != null && !job.isActive)
// 異常情況下
continuation.resumeWithException(job.getCancellationException())
else {
val exception = getExceptionalResult(state)
if (exception != null)
// 異常情況下
continuation.resumeWithStackTrace(exception)
else
// 正常情況下走到這一步
continuation.resume(getSuccessfulResult(state))
}
}
...
}
}
@InlineOnly public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
internal abstract class BaseContinuationImpl(...) {
// 實(shí)現(xiàn) Continuation 的 resumeWith,并且是 final 的,不可被重寫(xiě)
public final override fun resumeWith(result: Result<Any?>) {
...
val outcome = invokeSuspend(param)
...
}
// 由編譯生成的協(xié)程相關(guān)類來(lái)實(shí)現(xiàn),例如 setUpUI$1
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
}
最終調(diào)用到 continuation.resumeWith() 而 resumeWith() 中會(huì)調(diào)用 invokeSuspend,即之前編譯器生成的 SuspendLambda 中的 invokeSuspend 方法
final class setUpUI$1 extends SuspendLambda implements Function2{
public final Object invokeSuspend(Object result) {
switch (this.label) {
case 0:
doSomethingElse()
// 新建并啟動(dòng) async 協(xié)程
Deferred async$default = BuildersKt.async$default(coroutineScope, (CoroutineContext) Dispatchers.getDefault(), null, (Function2) new 1(null), 2, null);
this.label = 1;
// 如果 async 協(xié)程還沒(méi)完成為掛起狀態(tài) 則直接返回,等待下次喚醒重入
if (async$default.await(this) == coroutine_suspended) {
return coroutine_suspended;
}
break;
case 1:
val data = result;
processData(data)
break;
}
}
}
這段代碼是一個(gè)狀態(tài)機(jī)機(jī)制,每一個(gè)掛起點(diǎn)都是一種狀態(tài),協(xié)程恢復(fù)只是跳轉(zhuǎn)到下一個(gè)狀態(tài),掛起點(diǎn)將執(zhí)行過(guò)程分割成多個(gè)片段,利用狀態(tài)機(jī)的機(jī)制保證各個(gè)片段按順序執(zhí)行。
如果沒(méi)有掛起點(diǎn)就只有一個(gè)初始狀態(tài),類似于callback回調(diào),所以對(duì)應(yīng)了之前我們分析的非阻塞的異步底層實(shí)現(xiàn)其實(shí)也是一種callback回調(diào),只不過(guò)有多個(gè)掛起點(diǎn)時(shí)就會(huì)有多個(gè)callback回調(diào),我們把多個(gè)callback回調(diào)封裝成了一個(gè)狀態(tài)機(jī)。
協(xié)程的掛起
從協(xié)程的調(diào)度過(guò)程我們知道,調(diào)度后會(huì)到編譯器生成的 SuspendLambda 的 invokeSuspend 方法中的一個(gè)掛起方法,以例子中的await為例
if (async$default.await(this) == coroutine_suspended) {
//目前還在掛起中,則return等待掛起結(jié)束后的invokeSuspend
return coroutine_suspended;
}
async 也是一個(gè)協(xié)程,如果狀態(tài)為掛起coroutine_suspended,則執(zhí)行流直接 return 返回,如果已達(dá)到完成狀態(tài)直接跳轉(zhuǎn)下一個(gè)狀態(tài) case 1 最終走完整個(gè)協(xié)程代碼塊。
這里需要注意的是:
啟動(dòng)一個(gè)新的協(xié)程并不會(huì)掛起當(dāng)前協(xié)程,只有當(dāng)使用庫(kù)函數(shù) await、yield方法時(shí)才會(huì)將當(dāng)前的協(xié)程掛起。
協(xié)程掛起并不會(huì)阻塞線程,線程在掛起點(diǎn) return 后可以去執(zhí)行其他的代碼塊。
協(xié)程的掛起過(guò)程很簡(jiǎn)單,代碼塊直接返回,當(dāng)前狀態(tài)保存在狀態(tài)機(jī) SuspendLambda 中,可以想象到協(xié)程恢復(fù)的時(shí)候也是調(diào)用 SuspendLambda 的 invokeSuspend 從而進(jìn)入下一個(gè)狀態(tài)繼續(xù)執(zhí)行的。
delay 的實(shí)現(xiàn)
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
/** Returns [Delay] implementation of the given context */
internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay
internal actual val DefaultDelay: Delay = DefaultExecutor
delay 使用suspendCancellableCoroutine掛起協(xié)程,而協(xié)程恢復(fù)的一般情況下是關(guān)鍵在DefaultExecutor.scheduleResumeAfterDelay(),其中實(shí)現(xiàn)是schedule(DelayedResumeTask(timeMillis, continuation)),其中的關(guān)鍵邏輯是將 DelayedResumeTask 放到 DefaultExecutor 的隊(duì)列最后,在延遲的時(shí)間到達(dá)就會(huì)執(zhí)行 DelayedResumeTask,那么該 task 里面的實(shí)現(xiàn)是什么:
override fun run() {
// 直接在調(diào)用者線程恢復(fù)協(xié)程
with(cont) { resumeUndispatched(Unit) }
}
yield 的實(shí)現(xiàn)
yield()的作用是掛起當(dāng)前協(xié)程,然后將協(xié)程分發(fā)到 Dispatcher 的隊(duì)列,這樣可以讓該協(xié)程所在線程或線程池可以運(yùn)行其他協(xié)程邏輯,然后在 Dispatcher 空閑的時(shí)候繼續(xù)執(zhí)行原來(lái)協(xié)程。簡(jiǎn)單的來(lái)說(shuō)就是讓出自己的執(zhí)行權(quán),給其他協(xié)程使用,當(dāng)其他協(xié)程執(zhí)行完成或也讓出執(zhí)行權(quán)時(shí),一開(kāi)始的協(xié)程可以恢復(fù)繼續(xù)運(yùn)行。
看下面的代碼示例:
fun main(args: Array<String>) = runBlocking<Unit> {
launch {
repeat(3) {
println("job1 repeat $it times")
yield()
}
}
launch {
repeat(3) {
println("job2 repeat $it times")
yield()
}
}
}
通過(guò)yield()實(shí)現(xiàn) job1 和 job2 兩個(gè)協(xié)程交替運(yùn)行,輸出如下:
job1 repeat 0 times
job2 repeat 0 times
job1 repeat 1 times
job2 repeat 1 times
job1 repeat 2 times
job2 repeat 2 times
現(xiàn)在來(lái)看其實(shí)現(xiàn):
public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
val context = uCont.context
// 檢測(cè)協(xié)程是否已經(jīng)取消或者完成,如果是的話拋出 CancellationException
context.checkCompletion()
// 如果協(xié)程沒(méi)有線程調(diào)度器,或者像 Dispatchers.Unconfined 一樣沒(méi)有進(jìn)行調(diào)度,則直接返回
val cont = uCont.intercepted() as? DispatchedContinuation<Unit> ?: return@sc Unit
if (!cont.dispatcher.isDispatchNeeded(context)) return@sc Unit
// dispatchYield(Unit) 最終會(huì)調(diào)用到 dispatcher.dispatch(context, block) 將協(xié)程分發(fā)到調(diào)度器隊(duì)列中,這樣線程可以執(zhí)行其他協(xié)程
cont.dispatchYield(Unit)
COROUTINE_SUSPENDED
}
所以注意到,yield()需要依賴協(xié)程的線程調(diào)度器,而調(diào)度器再次執(zhí)行該協(xié)程時(shí),會(huì)調(diào)用resume來(lái)恢復(fù)協(xié)程運(yùn)行。
現(xiàn)在來(lái)看封裝異步邏輯為掛起函數(shù)的關(guān)鍵是用suspendCoroutineUninterceptedOrReturn函數(shù)包裝,然后在異步邏輯完成時(shí)調(diào)用resume手動(dòng)恢復(fù)協(xié)程。