为何不给Kotlin async指定Dispatcher时代码会串行执行?
现象复现
并行执行场景
运行以下代码时,协程会并行执行,输出内容相互交织:
In thread DefaultDispatcher-worker-2 B
BBIn thread DefaultDispatcher-worker-1 A
ABABAABBAAABABAABABABABABABABABAABABABAABBABABAABABABABABABABABABABABABABABABABABABABABABABABABABABABABABAABABABABABAAABBABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABAB
BBBBB
对应的代码:
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking fun main() { fun long(s: String): Int { println("In thread ${Thread.currentThread().name} $s ") repeat(100_000) { if (it % 1000 == 0) { print(s) } } println() return 1 } suspend fun doIt() = coroutineScope { val a = async(Dispatchers.Default) { long("A") } val b = async(Dispatchers.Default) { long("B") } } runBlocking { doIt() } }
串行执行场景
如果移除async调用中的Dispatchers.Default,代码会变为串行执行,输出按顺序出现:
In thread main A
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
In thread main B
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
原因分析
核心逻辑在于协程调度器(Dispatcher)决定了协程的执行线程池,以及是否具备并行能力:
- 指定Dispatchers.Default时:
Dispatchers.Default是基于共享线程池的调度器,默认线程数等于CPU核心数(至少2个)。- 两个
async块会被分配到不同的工作线程执行,long函数这类CPU密集型任务会同时运行,因此输出交织,呈现并行效果。
- 不指定Dispatcher时:
async会继承当前协程的上下文。这里doIt在runBlocking的上下文执行,而runBlocking默认使用调用它的线程(此处是main线程)作为执行线程,且该上下文是单线程的。long是普通阻塞同步代码,没有任何挂起点(suspend函数调用),第一个async会持续占用main线程直到执行完毕,才会轮到第二个async运行,最终表现为串行。
是否必须指定Dispatcher才能实现并行?
不是必须,但需要满足以下任一条件:
- 显式指定支持多线程的调度器(如
Dispatchers.Default、Dispatchers.IO),让协程能分配到不同线程并行执行; - 当前协程的上下文本身就是多线程调度器(比如在
launch(Dispatchers.Default)内部启动async),此时async会继承该上下文,无需额外指定。
注意:CPU密集型任务优先用Dispatchers.Default,IO密集型任务(如网络请求、文件读写)更适合用Dispatchers.IO。
内容的提问来源于stack exchange,提问作者PaulNUK

