当 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 中执行断言时,您没有查看实际的当前协程上下文,您实际上是从 runBlocking
的 CoroutineScope
.
它直接在 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.
}
在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 中执行断言时,您没有查看实际的当前协程上下文,您实际上是从 runBlocking
的 CoroutineScope
.
它直接在 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.
}