前言
Kotlin
是一种在Java
虚拟机上运行的静态类型编程语言,被称之为Android
世界的Swift
,在Google
I/O2017中,Google
宣布Kotlin
成为Android
官方开发语言
回顾
在 Kotlin中的协程(一) 中我们探讨了以下问题
协程的特点
协程的介绍
协程中的一些元素概念
如何创建一个协程
协程不同的调度模式
协程不同的执行模式
非阻塞式挂起
结构化并发
当我们了解了上述知识点以后,已经对协程有了一些基本的了解,但是对协程其他的的一些问题有点模糊,还是不明白如何使用协程,以及为什么会有协程等其他疑问,本文将继续探讨其他的协程问题
CoroutineContext
/**
* Persistent context for the coroutine. It is an indexed set of [Element] instances.
* An indexed set is a mix between a set and a map.
* Every element in this set has a unique [Key].
*/
@SinceKotlin("1.3")
public interface CoroutineContext
协程上下文是协程中的重要概念,它管理了协程的生命周期,线程调度,异常处理等功能
CoroutineContext中的元素
我们 可以在每个协程块中访问CoroutineContext
,它是Element
的索引集,可以使用索引Key
来访问上下文中的元素
Job
public interface Job : CoroutineContext.Element
协程的后台工作,可用来将协程取消
默认值: null,比如GlobalScope中的Job为null,
CoroutineExceptionHandler
public interface CoroutineExceptionHandler : CoroutineContext.Element
错误处理
ContinuationInterceptor
public interface ContinuationInterceptor : CoroutineContext.Element
负责线程的输入/输,Dispatcher
继承于CoroutineDispatcher
,我们通常使用Dispatcher
去指定对应的协程执行线程
CoroutineName
协程的名称,一般用于调试
val job = GlobalScope.launch {
coroutineContext[Job]
coroutineContext[CoroutineExceptionHandler]
coroutineContext[ContinuationInterceptor]
coroutineContext[CoroutineName]
}
自定义CoroutineContext
你可以自定义一个Context
当启动一个协程时,比如你需要自定义的CoroutineExceptionHandler
以及执行线程等
//自定义CoroutineContext
val errorHandle = CoroutineExceptionHandler { context, error ->
Log.e("Mike","coroutine error $error")
}
//指定自定义CoroutineContext
GlobalScope.launch(context = errorHandle) {
delay(2000)
throw Exception("test")
}
打印结果
coroutine error java.lang.Exception: test
Context
也可以支持多个上下文元素的拼接,使用+/plus
运算符
val job = Job()
val dispatcher = Dispatchers.IO
val errorHandle = CoroutineExceptionHandler { context, error ->
Log.e("Mike","coroutine error $error")
}
//自定义CoroutineContext集
val context = job + dispatcher + errorHandle
GlobalScope.launch(context = context) {
delay(2000)
throw Exception("test")
}
上下文的切换
可以使用withContext
,函数来改变协程的上下文,而仍然驻留在相同的协程中,当执行完withContext
块之后又会返回到原始调度中
GlobalScope.launch {
val res = async {
Log.e("Mike","get respone in ${Thread.currentThread().name}")
delay(2000)
"resposne data"
}
val response = res.await()
//执行Dispatchers.Main上下文
withContext(Dispatchers.Main){
Log.e("Mike","display respone $response in ${Thread.currentThread().name}")
}
}
打印结果
get respone in DefaultDispatcher-worker-3
//两秒后
display respone resposne data in main
CoroutineContext的创建
- 默认的
CoroutineContext
我们之前可以直接通过CoroutineScope
的launch
和async
去创建一个协程,当我们直接使用
GlobalScope.launch {}
它会有一个默认的EmptyCoroutineContext
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
-
手动传入的
CoroutineContext
我们可以自己构建一个CoroutineContext
传入进行指定 -
继承
当我们在一个协程中创建了另一个子协程的时候,会发生什么,我们可以先看看launch
函数的实现
GlobalScope.launch {
launch {
}
}
当子协程创建时调用launch
函数,传入子Context
或者默认
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
}
然后会调用newCoroutineContext(context)
,将子Context
与父coroutineContext
进行组装
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
这是Context
的plus规则,位于CoroutineContext
中,也就是说子Context
是由父Context
和子构建的Context
共同决定的,
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
加号操作符返回一个包含两个上下文所有 elements
的上下文集合,当两个上下文有 key 值相同的 element 时,它将丢弃掉操作符左侧上下文(父)中的 element
,也就是子上下文优先,如果子上下文为默认的上下文EmptyCoroutineContext,则返回父上下文
GlobalScope.launch(Dispatchers.IO + errorHandling) {
Log.e("Mike","------Parent context--->${coroutineContext[CoroutineExceptionHandler]}---${coroutineContext[ContinuationInterceptor]}")
launch(Dispatchers.Main) {
Log.e("Mike","------child context--->${coroutineContext[CoroutineExceptionHandler]}---${coroutineContext[ContinuationInterceptor]}")
}
}
//打印结果
------Parent context--->errorHandling---IO
------child context--->errorHandling---Main
总结
-
CoroutineContext
表示协程的上下文,包含此协程的相关信息,主要包含四个元素Job
,CoroutineExceptionHandler
,ContinuationInterceptor
,CoroutineName
它们都实现了CoroutineContext.Element
接口 -
CoroutineContext
可以自由组装使用运算符+/plus
的方式进行拼接,并在启动协程的时候指定 - 当在协程中刚创建一个子协程时,子协程的上下文则由指定的子协程作用域(或默认)与父上下文组合生成
- 可以使用
withContext
切换协程所执行的上下文,执行完毕之后又会回到原始上下文中
网友评论