跳至主要內容

使用调度器

guodongAndroid大约 3 分钟

前言

现在你已经了解有哪些调度器,是时候学习如何使用它们了。要导入本章的入门项目,首先打开 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 项目。