如果没有默认值,为什么可以在不提供 CoroutineContext 的情况下调用 runBlocking?

Why can `runBlocking` be invoked without providing a CoroutineContext, if there's no default value for it?

我总是检查我使用的东西的实现。

目前我使用的注入库不支持可暂停函数 (Koin),因此,仅(即使不鼓励)引导应用程序,我有时会使用 runBlocking。

为了拥有更丰富的日志,我用一些信息丰富了协程上下文,但在大多数上下文更改(launchasyncrunBlocking等等)。

特别是,鉴于非挂起方法无法访问 CoroutineContext,我非常好奇 runBlocking 从哪里获取它。

您可以像这样使用runBlocking

runBlocking {...}

然而,当我检查它的实现时,它有两个参数:a CoroutineContext 和要执行的挂起块。 None个参数有默认值,为什么不传就可以调用呢?我真的不明白!

此外,页面上说默认值为 EmptyCoroutineContext 但代码文档说了一些关于事件循环的内容。

那我再问一遍?为什么我可以在不传递值的情况下调用它,实际默认值是多少?

默认情况下 runBlocking() 从一个空协程上下文开始。

context 没有默认值这一事实确实令人困惑和奇怪。我认为(但我不是 100% 确定)这是因为通过 ctrl+clicking on runBlocking() 我们去实施,所以 actual 定义。但是代码是根据 expect 声明编译的。我没有找到直接在 IntelliJ 中查看 expect 声明的简单方法,但可以找到 here:

public expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T

我们可以看到,这个声明中的context有一个默认值。不过,这在使用 IntelliJ 时确实令人困惑。

关于提到的事件循环:是的,runBlocking() 创建(或 re-uses)一个事件循环,但我看不出它与协程上下文有什么关系。

请记住,您传递给 runBlockinglaunch 等协程构建器的上下文 而不是 在协程中实际可用的上下文,但它的 父级 。构建器添加自己的项目并将它们合并到您提供的上下文中。

runBlocking 使用它自己的调度程序,它仅在 runBlocking 函数调用的生命周期内存在。您可以在 runBlocking 主体内的协程上下文中找到此调度程序,例如使用此代码:

import kotlinx.coroutines.runBlocking

fun main() {
    runBlocking {
        val ctxElems = coroutineContext.fold(mutableListOf<Pair<Any, Any>>()) { list, element ->
            list.also { it.add(element.key to element) }
        }
        for ((key, value) in ctxElems) {
            println("${key::class.qualifiedName}: $value")
        }
    }
}

这会打印

kotlinx.coroutines.CoroutineId.Key: CoroutineId(1)
kotlinx.coroutines.Job.Key: "coroutine#1":BlockingCoroutine{Active}@12843fce
kotlin.coroutines.ContinuationInterceptor.Key: BlockingEventLoop@3dd3bcd

(协程调度器属于更广泛的延续拦截器类别)

你问题的另一部分,为什么你不必传入看似 non-default 的参数,在别处得到了回答。基本上,它是 IDE 和 expectedactual 声明的产物。相关的声明是 expectedactual 是一个实现细节,但是 IDE 带你到那一个。