CoroutineScope取消时delay未抛出CancellationException的原因
delay函数的文档说明:“该挂起函数是可取消的。如果当前协程的Job在该挂起函数等待期间被取消或完成,此函数会立即以CancellationException恢复执行。”
但实际测试中未观察到该异常抛出,delay反而等待了完整的10秒才继续执行,这是为什么?
测试代码
import kotlinx.coroutines.* import java.util.concurrent.CancellationException class MyClass { private val scope = CoroutineScope(SupervisorJob() + CoroutineName("X coroutine")) init { scope.launch { delay(500) println("cancelling the scope") scope.cancel(CancellationException("cancelling the scope")) } } suspend fun callScopedFunction( func: suspend CoroutineScope.() -> Int ): Int { if (scope.isActive) { println("callScopedFunction scope is active") } return scope.func() } } fun main() { runBlocking { val result = MyClass().callScopedFunction { println("coroutineContext.job.isCancelled: ${coroutineContext.job.isCancelled}") delay(10000) println("coroutineContext.job.isCancelled: ${coroutineContext.job.isCancelled}") 250 } println(result) } }
输出结果
callScopedFunction scope is active coroutineContext.job.isCancelled: false cancelling the scope coroutineContext.job.isCancelled: true 250
原因分析
1. 混淆了两个不同的Job状态
你在func lambda中打印的coroutineContext.job.isCancelled,指向的是MyClass内部scope的Job状态,而非执行delay的协程的Job状态。
因为func是CoroutineScope.()的扩展函数,lambda里的coroutineContext等价于接收者scope的上下文属性。当你调用scope.cancel()后,scope的Job被取消,所以这个值会变成true,但这和执行delay的协程无关。
2. delay只检查当前执行协程的Job
delay函数的可取消性,是针对执行它的那个协程的Job而言的。在你的代码中,delay(10000)是在runBlocking启动的协程中执行的——这个协程的Job从未被取消,所以delay会正常等待10秒,不会抛出异常。
3. scope.cancel()的作用范围有限
scope.cancel()只会取消scope的Job,以及所有通过scope.launch/scope.async等构建器启动的子协程。但你的func并没有在scope的协程上下文中执行:scope.func()只是把scope作为接收者调用扩展函数,并没有将执行逻辑切换到scope的协程上下文里,因此func的执行不受scope.cancel()的影响。
修复方案
要让delay在scope取消时抛出异常,需要将func的执行切换到scope的协程上下文中,比如使用async或withContext:
// 修改callScopedFunction方法 suspend fun callScopedFunction( func: suspend CoroutineScope.() -> Int ): Int { if (scope.isActive) { println("callScopedFunction scope is active") } // 用async将func切换到scope上下文执行,再await获取结果 return scope.async { func() }.await() }
修改后,当scope.cancel()被调用时,func所在的协程会被取消,delay会立即抛出CancellationException,符合文档描述的行为。
内容的提问来源于stack exchange,提问作者Alexander

