使用调度器
前言
现在你已经了解有哪些调度器,是时候学习如何使用它们了。要导入本章的入门项目,首先打开 IntelliJ,然后选择 导入项目,然后前往 context-switch-and-dispatching/projects/starter 文件夹,然后选择 context-switch-and-dispatching 项目。打开 Main.kt 可以看到以下代码:
fun main() {
GlobalScope.launch {
println("This is a coroutine")
}
Thread.sleep(50)
}
你无法透露有关幕后发生的线程或调度的任何信息。让我们回顾一下 launch
函数的签名:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
这里,第一个参数很重要。如果你不指定其他的值,它将默认使用 EmptyCoroutineContext
。你已经了解到,上下文 定义了协程的启动方式、位置、错误处理及其生命周期。EmptyCoroutineContext
没有定义任何的错误处理机制,它也没有父上下文,同时它使用默认的生命周期管理并且也没有 CoroutineInterceptor
,所以它使用 Dispatchers.Default
。
从你对调度器的了解来看,这意味着上述协程将使用预定义的线程池来完成其工作。如果你想使用其他的调度器,只需将其传入来替换默认的 EmptyCoroutineContext
。一旦你这样做,这个调度器将被用作这个协程的上下文,并且它将支配 所有的线程。
接下来,稍微改变下示例。与其打印一些虚拟文本,不如让协程打印它所在的线程,将之前的代码替换为:
fun main() {
GlobalScope.launch { println(Thread.currentThread().name) }
Thread.sleep(50)
}
如果你运行上述代码,它将打印与下面类似的输出:
DefaultDispatcher-worker-1
这有助于你对默认调度器的了解。以下代码你会得到同样的结果:
fun main() {
GlobalScope.launch(context = Dispatchers.Default) {
println(Thread.currentThread().name)
}
Thread.sleep(50)
}
如果你传入了不同的调度器,你将得到不同的结果。比如传入 Dispatchers.Unconfined
的实例。所以如果你有以下代码:
fun main() {
GlobalScope.launch(context = Dispatchers.Unconfined) {
println(Thread.currentThread().name)
}
Thread.sleep(50)
}
它将打印出 main
作为它的线程。这是因为无限制的调度器只接收代码运行的线程,并将协程附加到该线程。但是,如果你不想将代码 受限 在 Coroutines API 为你提供的特定约束集上,并且需要指定线程的任务,该怎么办呢?
创建一个工作窃取(抢占)执行器
译者注:
Executors.newWorkStealingPool()
是 Java 8 中新增的线程池,基于 work-stealing 算法。
使用标准的 Coroutines API,你同样可以为协程创建新的线程或者线程池。这依赖于创建一个新的 执行器。执行器执行给定任务的对象。它们通常与 Runnables 绑定,因为它们将任务包装在需要执行的 Runnable
中。例如,创建一个工作窃取(抢占)执行器意味着它将使用所有可用的资源,以实现您可以定义的一定程度的并行性。要使用工作窃取(抢占)执行器,请将之前的代码替换为以下内容:
fun main() {
val executorDispatcher = Executors
.newWorkStealingPool()
.asCoroutineDispatcher()
GlobalScope.launch(context = executorDispatcher) {
println(Thread.currentThread().name)
}
Thread.sleep(50)
}
如果你运行上述代码,它将打印与下面类似的输出:
ForkJoinPool-1-worker-9
执行器使用这里的所有可用资源,如 ForkJoinPool 线程池,来完成你的任务。完成任务后,它可以将占用的资源重新分配给应用程序的其他部分。如果你将该执行器并行级别设置为 四,并且你在许多协程中使用该执行器,只要有工作要分发,它就会分配资源以实现四次并行执行。
如果你想在最终项目中查看此最终示例,请使用 IntelliJ 导入本章的最终项目,然后选择 导入项目,然后前往 context-switch-and-dispatching/projects/final 文件夹,选择 context-switch-and-dispatching 项目。