一、CoroutineContext继承关系
- CoroutineScope接口
- 唯一成员变量CoroutineContext
- 子类Element接口
- Job
- CoroutineDispatcher
- CoroutineName
- CoroutineExceptionHandler
- 子类Element接口
- 唯一成员变量CoroutineContext
1.1、接口设计
CoroutineContext 的 API 设计和 Map 十分类似:
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public operator fun plus(context: CoroutineContext): CoroutineContext {}
public fun minusKey(key: Key<*>): CoroutineContext
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public interface Key<E : Element>
}
1.2、操作符重载
@ExperimentalStdlibApi
fun main() = runBlocking {
val job = Job()
val dispatcher: ExecutorCoroutineDispatcher = Executors.newSingleThreadExecutor {
Thread(it, "MySingleThread").apply { isDaemon = true }
}.asCoroutineDispatcher()
val scope = CoroutineScope(job + dispatcher) // 操作符重载
scope.launch {
log(scope.coroutineContext[Job] === job) // 操作符重载
log(scope.coroutineContext[CoroutineDispatcher] === dispatcher)
log(scope.coroutineContext.get(CoroutineDispatcher) === dispatcher)
log(coroutineContext[ExecutorCoroutineDispatcher] === dispatcher)
}
delay(500L)
}
fun log(text: Any) = println("$text - ${Thread.currentThread().name}".trimIndent())
在上面的代码中,我们:
- 使用了
job + dispatcher这样的方式,创建CoroutineScope - 使用了
coroutineContext[XX]这样的方式,访问当前协程所对应的XX
代码之所以这么写,是因为 CoroutineContext 的 plus/get 方法支持操作符重载:
public operator fun plus(context: CoroutineContext): CoroutineContext
public operator fun <E : Element> get(key: Key<E>): E?
- 用
operator修饰plus()方法后,就可以用+来重载这个方法- 比如,集合之间的合并操作:
list3 = list1 + list2、map3 = map1 + map2
- 比如,集合之间的合并操作:
- 用
operator修饰get()方法后,就可以用[]来重载这个方法- 比如,以数组下标的方式访问集合的元素:
list[0]、map[key]
- 比如,以数组下标的方式访问集合的元素:
如果 plus/get 方法声明中去掉了关键字 operator,就只能使用下面的方式了:
job.plus(dispatcher) // 或 dispatcher.plus(job)
scope.coroutineContext.get(CoroutineDispatcher)
Kotlin 中的集合与数组的访问方式,之所以可以保持一致,就是依赖于操作符重载。实际上,Kotlin 官方的源代码当中大量使用了操作符重载来简化代码逻辑,而 CoroutineContext 就是一个最典型的例子。
二、Job
基本上每启动一个协程就会产生对应的Job,例如
lifecycleScope.launch {
}
launch返回的就是一个Job,它可以用来管理协程。
CoroutineContext中的Job和父级上下文中的Job永远不会是同一个实例,因为一个新的coroutine总是得到一个Job的新实例。
一个
Job中可以关联多个子Job,同时它也提供了通过外部传入parent的实现。public fun Job(parent: Job? = null): Job = JobImpl(parent)这个很好理解,当传入
parent时,此时的Job将会作为parent的子Job。
提供了六种状态来表示协程的运行状态。
New: 创建Active: 运行Completing: 已经完成等待自身的子协程Completed: 完成Cancelling: 正在进行取消或者失败Cancelled: 取消或失败
这六种状态Job对外暴露了三种状态,它们随时可以通过Job来获取
public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean
所以如果你需要自己来手动管理协程,可以通过下面的方式来判断当前协程是否在运行。
while (job.isActive) {
// 协程运行中
}
一般来说,协程创建的时候就处在Active状态,但也有特例。
例如我们通过launch启动协程的时候可以传递一个start参数
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
如果这个start传递的是CoroutineStart.LAZY,那么它将处于New状态。可以通过调用start或者join来唤起协程进入Active状态。
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| cancel / fail |
| +----------------+
| |
V V
+------------+ finish +-----------+
| Cancelling | --------------------------------> | Cancelled |
+------------+ +-----------+
上面已经提及到一个Job可以有多个子Job,所以一个Job的完成都必须等待它内部所有的子Job完成;对应的cancel也是一样的。
默认情况下,如果内部的子Job发生异常,那么它对应的parent Job与它相关连的其它子Job都将取消运行。俗称连锁反应。
我们也可以改变这种默认机制,Kotlin提供了SupervisorJob来改变这种机制。这种情况还是很常见的,例如用协程请求两个接口,但并不想因为其中一个接口失败导致另外的接口也不请求,这时就可以使用SupervisorJob来改变协程的这种默认机制。
使用很简单,在我们创建CoroutineContext的时候加入SupervisorJob即可。例如在上面提到过的lifecycleScope,内部就使用到了SupervisorJob
val newScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
如果有些任务你并不想被手动取消,可以使用
NonCancellable作为任务的CoroutineContext。如果需要
Job获取协程的返回结果,可以通过Deferred来实现,它是Job的一个子类,所以也拥有Job所用功能。同时额外提供await方法来等待协程结果的返回。
Deferred可以通过CoroutineScope.async创建。
三、CoroutineDispatcher
public actual object Dispatchers {
@JvmStatic public val IO: CoroutineDispatcher = DefaultIoScheduler
@JvmStatic public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
@JvmStatic public actual val Default: CoroutineDispatcher = DefaultScheduler
@JvmStatic public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
}
- Dispatchers.Main:只在 Android、Swing 之类的 UI 平台才有意义,在普通的 JVM 工程中是无法直接使用的
- Dispatchers.Unconfined:无限制,当前协程可能运行在任意线程之上
- Dispatchers.Default:用于 CPU 密集型任务的线程池,线程个数与 CPU 核心数量一致,最小为 2
- Dispatchers.IO:用于 IO 密集型任务的线程池,线程数量一般比较多,可通过参数
kotlinx.coroutines.io.parallelism配置
1、IO密集型任务
一般来说:文件读写、DB读写、网络请求等
2、CPU密集型任务
一般来说:计算型代码、Bitmap转换、Gson转换等
四、CoroutineName
用于指定协程的名称
五、CoroutineExceptionHandler
主要负责处理协程当中的异常
public interface CoroutineExceptionHandler : CoroutineContext.Element {
public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
public fun handleException(context: CoroutineContext, exception: Throwable)
}
如果我们要自定义异常处理器,只需要实现 handleException() 方法即可:
CoroutineScope(Dispatchers.IO + SupervisorJob() + CoroutineName("aaa")
+ CoroutineExceptionHandler { coroutineContext, throwable ->
})