使用调度程序运行异步 Kotlin 代码

Ste*_*erl 2 kotlin kotlin-coroutines

我正在尝试用 Kotlin 协程来理解异步代码执行。

为什么在启动异步代码块时必须指定调度程序?

以下代码块

fun doInParallel(): Unit = runBlocking {
    coroutineScope {
        launch { println("start A").also { Thread.sleep(1_000) }.also { println("finish A") } }
        launch { println("start B").also { Thread.sleep(1_000) }.also { println("finish B") } }
    }
}
Run Code Online (Sandbox Code Playgroud)

总是会打印

start A
finish A
start B
finish B
Run Code Online (Sandbox Code Playgroud)

这不是我所期望的。

仅在显式指定调度程序后,异步代码块才会并行运行:

fun doInParallel(): Unit = runBlocking {
    coroutineScope {
        launch(Dispatchers.Default) { println("start A").also { Thread.sleep(1_000) }.also { println("finish A") } }
        launch(Dispatchers.Default) { println("start B").also { Thread.sleep(1_000) }.also { println("finish B") } }
    }
}
Run Code Online (Sandbox Code Playgroud)

将打印

start A
start B
finish A
finish B
Run Code Online (Sandbox Code Playgroud)

为什么我必须包括Dispatcher.Default

我正在使用org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-Beta(如果有任何相关性的话)

Ram*_*man 5

协程应该挂起,而不是阻塞

而不是Thread.sleep(...)使用delay(...).

Thread.sleep阻塞线程,对于协程来说,你想要挂起线程,而不是阻塞它。当使用像这样的挂起函数从一个协程中挂起一个线程时delay,同一个线程可以自由地为另一个协程提供服务——在本例中是由第二个协程启动的线程launch

在第二个示例中指定调度程序具有不同行为的原因是,它coroutineScope从父作用域继承协程上下文,在本例中为runBlocking. 协程构建器中的默认调度程序runBlocking已记录:

此构建器的默认 CoroutineDispatcher 是事件循环的内部实现,它处理此阻塞线程中的延续,直到此协程完成。

换句话说,在您的示例中,所有内容都在单个线程内运行:主线程(或正在调用的任何线程doInParallel)。与使用阻塞相结合Thread.sleep意味着代码以阻塞方式同步执行。

总结一下:

  1. 更改代码以打印线程名称,您将看到所有内容都在main线程中运行(除非您指定调度程序)。

  2. 从 using 更改Thread.sleepdelay修复根本问题,并允许单个线程运行两个启动的协程。

fun doInParallel(): Unit = runBlocking {
  coroutineScope {
    launch { println("${Thread.currentThread().name} start A").also { delay(1_000) }.also { println("${Thread.currentThread().name} finish A") } }
    launch { println("${Thread.currentThread().name} start B").also { delay(1_000) }.also { println("${Thread.currentThread().name} finish B") } }
  }
}
Run Code Online (Sandbox Code Playgroud)

如何运行阻塞代码?

要在协程内运行阻塞代码,请使用Dispatchers.IO,它是专门为将阻塞任务卸载到共享线程池而设计的。此外,根据 @George Leung 的评论,runInterruptible如果底层代码响应线程中断,则该方法可用于以可中断的方式调用指定的块。