文章目录
4.1 协程上下文调度器
通过前面的学习,我们了解到协程的基本使用,我们都知道最简单的方式启动协程就是通过GlobalScope.launch(){}
方式启动协程,那么我们看看这个方法到底做了什么吧
我们先看 GlobalScope 的实现:
//GlobalScope
@DelicateCoroutinesApi
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
// CoroutineScope
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
}
我们通过源码发现,GlobalScope 是继承自CoroutineScope,而CoroutineScope的 launch 函数,就是协程的入口,主要包含的参数有三个,分别是 CoroutineContext,CoroutineStart,以及一个挂起函数,返回值是一个 Job。这里我们先从 CoroutineContext这个参数讲起。
CoroutineContext是一个接口,内部有一个 Element 接口继承CoroutineContext,还有一个 Key 的接口,在CoroutineContext注释里,是这样描述CoroutineContext的,意思是CoroutineContext是一组 Element 的实例索引,每一个索引都有一个唯一的 Key。
我们看 Element 的实现类起始有很多,今天我们主要讲协程的任务分发调度器 --Dispatchers:
通过上面图示,我们可以看到 Kotlin 提供了四中 Dispatcher,分别是
- Default :顾名思义,Default 也就是默认值,当不设置任何调度器的时候,就默认为这个调度器
- Main :主调度器,也就是在主线程中使用此调度器,
- Unconfined :不受限制的调度器(比较复杂,后续说明)
- IO : 用于IO 操作的协程调度器
我们简单试一下这几个调度器的使用
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
launch { //不设置调度器
println("runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // 不受限的——将工作在主线程中
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // 将会获取默认调度器
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch (Dispatchers.IO){ // 将会获取IO调度器
println("IO : I'm working in thread ${Thread.currentThread().name}")
}
}
输出的结果为:
这里我们可以看到不设置调度器的时候,并没有使用DefaultDispatcher,而是使用了Main 调度器,这里是因为,子协程会默认使用启动该协程的CoroutineScope 中的调度器。 runBlocking 使用的是 Main。可能还会有一个疑问,为什么 IO 调度器不是 IO 呢,这是因为 IO 调度器起始也是通过默认调度器里的线程池实现的,这个我们后续讲解原理。
注意
-
当协程在GlobalScope中启动时,使用的是由 Dispatchers.Default代表的默认调度器。 默认调度器使用共享的后台线程池。 所以
launch(Dispatchers.Default) { …… }
与GlobalScope.launch { …… }
使用相同的调度器。 -
当我们不想使用 kotlin默认的调度器,是可以自己创建线程调度器的,使用方法为
newSingleThreadContext(name: kotlin.String)
以及newFixedThreadPoolContext(nThreads: kotlin.Int, name: kotlin.String)
,这里有点像 Java 通过 Executors 创建线程池。(在这里为协程创建线程/线程池是非常消耗资源的,所以在不是用的时候需要及时释放)
launch (newSingleThreadContext("single")){
println("single : I'm working in thread ${Thread.currentThread().name}")
}
launch( newFixedThreadPoolContext(2,"multi")) {
println("multi : I'm working in thread ${Thread.currentThread().name}")
}