当 withContext 下的块调用另一个挂起块时保持协程上下文

Keep the coroutine context when a block under withContext calls another suspend block

在Kotlin中使用withContext时,如何在调用另一个挂起函数后保持新的上下文?

例如: https://pl.kotl.in/fkrnEkYZP

fun main() = runBlocking {
    val anotherBlock = suspend {
        assertSame(Dispatchers.IO, coroutineContext[CoroutineDispatcher], "current CoroutineDispatcher should be still Dispatchers.IO")
        // Exception in thread "main" java.lang.AssertionError: current CoroutineDispatcher should be still Dispatchers.IO. Expected <Dispatchers.IO>, actual <BlockingEventLoop@6863cde3> is not same.
    }
    withContext(Dispatchers.IO) {
        assertSame(Dispatchers.IO, coroutineContext[CoroutineDispatcher], "current CoroutineDispatcher should be Dispatchers.IO")
        anotherBlock()
    }
}

为什么 anotherBlock 中的 coroutineContext 与父级不同?

问题在于,在挂起 lambda 中执行断言时,您没有查看实际的当前协程上下文,您实际上是从 runBlockingCoroutineScope.

它直接在 withContext 中工作的原因是因为 withContext 提供了一个 CoroutineScope 作为 this - 它隐藏了 runBlocking 给出的那个,很幸运你选对了。

为了避免这个陷阱(当同时使用 suspend 函数和 CoroutineScope 时经常发生),当你想知道当前的协程上下文时,你应该使用 currentCoroutineContext()代码正在执行,这就是你的情况。

如果这样做,您会看到调度程序已正确继承:

fun main() = runBlocking {
    val anotherBlock = suspend {
        assertSame(Dispatchers.IO, currentCoroutineContext()[CoroutineDispatcher], "current CoroutineDispatcher should be still Dispatchers.IO in suspend lambda")
    }
    withContext(Dispatchers.IO) {
        assertSame(Dispatchers.IO, coroutineContext[CoroutineDispatcher], "current CoroutineDispatcher should be Dispatchers.IO")
        anotherBlock()
    }
}

coroutineContext 是 CoroutineScope 的一个 属性。由于您的 anotherBlock 挂起函数没有 CoroutineScope 接收器,您在 runBlocking lambda 的范围内调用 coroutineContext,因此它正在获取 runBlocking 的调度程序。

更改您的函数声明以使用扩展函数语法而不是 lambda,以便它可以拥有自己的 CoroutineScope。

suspend fun CoroutineScope.anotherBlock() {
    assertSame(Dispatchers.IO, coroutineContext[CoroutineDispatcher], "current CoroutineDispatcher should be still Dispatchers.IO")
    // Exception in thread "main" java.lang.AssertionError: current CoroutineDispatcher should be still Dispatchers.IO. Expected <Dispatchers.IO>, actual <BlockingEventLoop@6863cde3> is not same.
}