public interface CoroutineContext {
public operator fun get(key: Key): E?
public fun fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext{...}
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key
public interface Element : CoroutineContext {...}
}
public interface Element : CoroutineContext {
public val key: Key<*>
public override operator fun get(key: Key): E? = if (this.key == key) this as E else null
public override fun 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
}
Element 定义在 CoroutineContext 中, 是它的内部接口,不过这并不是重点,重点有两个,我们依次来分析。
public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element
创建元素不难,提供对应的 Key 即可。
public data class CoroutineName(
val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
public companion object Key : CoroutineContext.Key
override fun toString(): String = "CoroutineName($name)"
}
协程异常处理器的实现:
public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler =
object : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler {
override fun handleException(context: CoroutineContext, exception: Throwable) =
handler.invoke(context, exception)
}
public interface CoroutineExceptionHandler : CoroutineContext.Element {
public companion object Key : CoroutineContext.Key
public fun handleException(context: CoroutineContext, exception: Throwable)
}
之所以可以这样写是因为协程上下文进行了+号运算符重载。当然也可以这样做:
为协程上下文设定协程名:
GlobalScope.launch(CoroutineName("parent")) {
async(CoroutineName("child1")) {
Thread.sleep(2000)
return@async "response data"
}.await()
async(CoroutineName("child2")) {
Thread.sleep(2000)
return@async "response data"
}.await()
}
suspend fun main() {
var context: CoroutineContext = CoroutineName("CoroutineName01")
context += CoroutineExceptionHandler { coroutineContext, throwable ->
println("捕获到异常: $throwable 协程名字是:${coroutineContext[CoroutineName]}")
}
val job = GlobalScope.launch(context) {
println("协程名字是:${coroutineContext[CoroutineName]}")
withContext(Dispatchers.IO) {
println("协程名字是:${coroutineContext[CoroutineName]}")
Thread.sleep(5000)
throw Exception("发生了异常:Error!!!!!!!!!!!") // 抛出异常
}
}
job.join()
}
不管结果如何, Continuation
注意, context[CoroutineExceptionHandler] 中的 CoroutineExceptionHandler 实际上是异常处理类的伴生对象,也就是它在协程上下文中的 Key。
提示:要捕获协程的异常,除了使用 CoroutineExceptionHandler,依然可以使用try-catch在协程体内进行捕获,但使用CoroutineExceptionHandler可以统一捕获,不用到处 try-catch了。
fun CoroutineScope.safeLaunch(
onError: ((Throwable) -> Unit)? = null,
onLaunchBlock: () -> Unit
) {
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
onError?.invoke(throwable)
}
this.launch(exceptionHandler) {
onLaunchBlock.invoke()
}
}
GlobalScope.safeLaunch(onError = { throwable ->
println(throwable.message ?: "UnKnow Error")
}) {
println("执行在协程中...")
delay(1000L)
val num = 999/0
println("执行完毕...")
}
拦截器的 Key 是一个固定的值 Continuationlnterceptor, 程执行时会通过这个 Key 拿到拦截器并实现对 Continuation 的拦截。
从图 3-3 中可以清楚地 到,协程体在挂起点处先被拦截器,再被 SafeContinuation 保护了起来。想要让协程体真正恢复执行,先要过这两个过程,这也为协程支持更加复杂的调度逻辑提供了基础。除了打印日志,拦截器的作用还有很多,最常见的就是控制线程的切换,相关内容请考后续调度器实现的内容。
补充细节:
参考:
《深入理解Kotlin协程》- 2020年-机械工业出版社-霍丙乾