一、认识CoroutineContext
看一下CoroutineContext
的源码,如下:
@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>
//Element接口实现CoroutineContext
public interface Element : CoroutineContext {
//...代码略...
}
}
CoroutineContext
是一个接口,它的实现类常见的有:DefaultIoScheduler
,CoroutineDispatcher
,EmptyCoroutineContext
,MainCoroutineDispatcher
等等。CoroutineContext的接口设计,就跟集合 API 一样。准确来说,它的 API 设计和 Map 十分类似。
二、Dispatcher
我们用launch
启动协程时,第一个参数就是CoroutineContext
,如果不传的话默认就是EmptyCoroutineContext
,顾名思义,这就是一个空的上下文对象。而如果我们想要指定 launch
工作的线程池的话,就需要自己传 context
这个参数了,我们可以传入一个 Dispatcher
对象作为参数,比如下面的例子:
fun main() {
printMsg("start")
GlobalScope.launch(context = Dispatchers.IO) { //Dispatchers指定为IO线程(日志中复用了Dispatchers.default的线程)
printMsg("job start")
delay(500)
printMsg("job end")
}
Thread.sleep(1000)
printMsg("end")
}
//打印日志
2023-09-27T16:45:08.888464 main start
2023-09-27T16:45:08.964462500 DefaultDispatcher-worker-1 @coroutine#1 job start //Dispatchers.IO复用了Dispatchers.Default
2023-09-27T16:45:09.481660100 DefaultDispatcher-worker-1 @coroutine#1 job end //Dispatchers.IO复用了Dispatchers.Default
2023-09-27T16:45:09.962842700 main end
官方除了提供了 Dispatchers.IO
以外,还提供了 Dispatchers.Main
、 Dispatchers.Unconfined
、Dispatchers.Default
这几种内置 Dispatcher。下面介绍一 下:
Dispatchers.Main
它只在 UI 编程平台才有意义,在 Android、Swing 之类的平台上,一般只有 Main 线程才能用于 UI 绘制。这个 Dispatcher 在普通的 JVM 工程当中,是无法直接使用的。Dispatchers.Unconfined
无拘束的,当前协程可能运行在任意线程之上(因为线程不可控,生产环境不要用)。Dispatchers.Default
它是用于 CPU 密集型任务的线程池。一般来说,它内部的线程个数是与机器 CPU 核心数量保持一致的,不过它有一个最小限制2。Dispatchers.IO
它是用于 IO 密集型任务的线程池。它内部的线程数量一般会更多一些(比如 64 个),具体线程的数量我们可以通过参数来配置:kotlinx.coroutines.io.parallelism
。
需要特别注意的是,Dispatchers.IO
底层是可能复用 Dispatchers.Default
当中的线程的,比如上面的例子。
我们也可以自定义Dispatchers
,如下:
fun main() {
//自定义Dispatcher
val myDispatcher:ExecutorCoroutineDispatcher = Executors.newSingleThreadExecutor{
Thread(it,"MyDispatcher ").apply { isDaemon = true }
}.asCoroutineDispatcher() //扩展函数
//asCoroutineDispatcher()扩展函数的源码
public fun ExecutorService.asCoroutineDispatcher(): ExecutorCoroutineDispatcher =
ExecutorCoroutineDispatcherImpl(this)
GlobalScope.launch(myDispatcher) { //变化在这里:用自己的Dispatcher
//...
}
//日志:
2023-09-27T17:05:54.307281600 MyDispatcher @coroutine#1 job start //线程名:MyDispatcher
}
从这里我们也能看到,Dispatcher
的本质仍然还是线程。这也再次验证了我们之前的说法:协程运行在线程之上。
我们看看Dispatchers
与CoroutineContext
的代码关系:
三、万物皆有 Context
在 Kotlin 协程当中,但凡是重要的概念,都或多或少跟 CoroutineContext
有关系:Job
、Dispatcher
、CoroutineExceptionHandler
、CoroutineScope
,甚至挂起函数,它们都跟CoroutineContext
有着密切的联系。甚至,它们之中的 Job
、Dispatcher
、CoroutineExceptionHandler
本身,就是 Context
。
1、CoroutineScope
如果要调用 launch,就必须先有协程作用域,也就是CoroutineScope
。看launch
和CoroutineScope
的源码:
public fun CoroutineScope.launch( //CoroutineScope协程作用域扩展函数
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
}
public interface CoroutineScope {
public val coroutineContext: CoroutineContext //协程上下文
}
看 CoroutineScope
的源码会发现,它其实就是一个简单的接口,而这个接口只有唯一的成员,就是 CoroutineContext
。所以,CoroutineScope
只是对 CoroutineContext
做了一层封装而已,它的核心能力其实都来自于 CoroutineContext
。
CoroutineScope
最大的作用,就是可以方便我们批量控制协程:
fun main() {
runBlocking {
val scope = CoroutineScope(Job())
printMsg("scope start")
val job1 = scope.launch {
printMsg("job1 start")
delay(300) //job1的delay
printMsg("job1 end")
}
val job2 = scope.launch {
printMsg("job2 start")
delay(700) //job2的delay
printMsg("job2 end")
}
val job3 = scope.launch {
printMsg("job3 start")
delay(1500) //job3的delay
printMsg("job3 end")
}
delay(500) //第一次delay,大于job1的delay时间
//取消scope,就批量取消了job1,job2,job3
scope.cancel()
delay(2000)
printMsg("scope end")
}
}
//打印日志 job2 end 和 job3 end 没有输出
2023-09-28T14:08:28.543547600 main @coroutine#1 scope start
2023-09-28T14:08:28.553548600 DefaultDispatcher-worker-1 @coroutine#2 job1 start
2023-09-28T14:08:28.558545800 DefaultDispatcher-worker-2 @coroutine#3 job2 start
2023-09-28T14:08:28.560548900 DefaultDispatcher-worker-3 @coroutine#4 job3 start
2023-09-28T14:08:28.864393 DefaultDispatcher-worker-2 @coroutine#2 job1 end
2023-09-28T14:08:31.085238900 main @coroutine#1 scope end
2、Job
如果说 CoroutineScope
是封装了 CoroutineContext
,那么 Job 就是一个真正的CoroutineContext
了。源码如下:
public interface Job : CoroutineContext.Element {}
上面这段代码,Job
继承自 CoroutineContext.Element
,而CoroutineContext.Element
仍然继承自CoroutineContext
,这就意味着 Job
是间接继承自CoroutineContext
的。所以说,Job
确实是一个真正的 CoroutineContext
。
3、CoroutineName
CoroutineName
,当我们创建协程的时候,可以传入指定的名称,比如:
fun main() {
runBlocking {
launch(context = CoroutineName("MyFirstCoroutine")) { //指定协程的名字
printMsg("launch")
}
}
}
//打印日志
2023-09-28T14:46:16.505510500 main @MyFirstCoroutine#2 launch
看下CoroutineName
源码:
public data class CoroutineName(
val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
public companion object Key : CoroutineContext.Key<CoroutineName>
override fun toString(): String = "CoroutineName($name)"
}
public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element{} //CoroutineContext.Element
CoroutineName
也是继承了CoroutineContext
。
4、CoroutineExceptionHandler
CoroutineContext
当中,还有一个重要成员是 CoroutineExceptionHandler
,它主要负责处理协程当中的异常。源码如下:
public interface CoroutineExceptionHandler : kotlin.coroutines.CoroutineContext.Element {
public companion object Key : kotlin.coroutines.CoroutineContext.Key<kotlinx.coroutines.CoroutineExceptionHandler> {
}
public abstract fun handleException(context: kotlin.coroutines.CoroutineContext, exception: kotlin.Throwable): kotlin.Unit
}
CoroutineExceptionHandler
真正重要的,其实只有 handleException()
这个方法,如果我们要自定义异常处理器,我们就只需要实现该方法即可。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//打印协程名
System.setProperty("kotlinx.coroutines.debug", "on")
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
//这里输出日志
log("exception---> $throwable")
}
lifecycleScope.launch(context = exceptionHandler) {
val i = 2 / 0 //这里抛出异常
}
}
//打印日志的方法
fun log(msg: String) {
Log.d("TAG", "${Thread.currentThread().name} $msg")
}
}
//打印日志
main @coroutine#2 exception---> java.lang.ArithmeticException: divide by zero