协程中取消长耗时库函数时如何避免竞态条件
问题核心原因
该竞态的本质是取消信号的检查、长耗时库函数的启动、取消方法的调用三个操作没有原子性保证:普通布尔标志位(哪怕是volatile修饰的)只能保证内存可见性,标志位检查和后续函数启动之间存在不可消除的时间窗口,cancel信号可能刚好在这个窗口内触发,既没拦住函数启动,也没来得及调用库的取消方法。
正确实现方案
用原子状态机消除所有竞态窗口,不需要额外加重量级锁,性能开销极低,完全适配协程场景:
- 定义明确的生命周期状态,标记长耗时库函数的运行阶段
- 所有状态流转都用CAS(比较并交换)原子操作完成,不存在中间态
- 启动长耗时函数前先原子确认无取消信号,收到取消信号时根据当前状态判断是否需要调用库的取消方法
实现代码
import java.util.concurrent.atomic.AtomicReference import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch // 长耗时库函数的生命周期状态 private enum class LibraryTaskState { IDLE, // 未启动 RUNNING, // 运行中 CANCELLED, // 已收到取消请求 FINISHED // 已执行完成 } class TaskWrapper(private val scope: CoroutineScope) { private val taskState = AtomicReference(LibraryTaskState.IDLE) fun cancel() { // 原子性标记取消,同时拿到修改前的状态 val prevState = taskState.getAndSet(LibraryTaskState.CANCELLED) // 只有当任务已经在运行时,才需要调用库的取消方法 if (prevState == LibraryTaskState.RUNNING) { cancelSomeLongRunningCancellableLibraryFunction() } } fun run() { scope.launch(Dispatchers.Default) { someLongRunningFunction() // 原子性尝试标记任务为运行中:只有当前状态是IDLE(没收到取消)才会成功 val canStartTask = taskState.compareAndSet(LibraryTaskState.IDLE, LibraryTaskState.RUNNING) if (!canStartTask) { // 已经收到取消信号,直接跳过,不启动长耗时任务 return@launch } try { someLongRunningCancellableLibraryFunction() } finally { // 任务执行完(不管是正常结束还是被取消),标记为完成 taskState.set(LibraryTaskState.FINISHED) } } } // 以下是外部依赖的库函数,不可修改 private fun someLongRunningFunction() {} private fun someLongRunningCancellableLibraryFunction() {} private fun cancelSomeLongRunningCancellableLibraryFunction() {} }
逻辑验证(覆盖所有时序场景)
- 场景1:cancel调用时,协程还在执行
someLongRunningFunction():cancel会先把状态设为CANCELLED,等协程执行到启动前的CAS检查时,会发现状态不是IDLE,直接跳过启动长耗时函数,不会出现漏取消。 - 场景2:协程刚完成CAS标记为RUNNING,还没正式启动长耗时函数时cancel触发:cancel拿到的前序状态是RUNNING,会立刻调用库的取消方法,由于库的取消方法支持跨线程调用,哪怕函数刚完成启动,也能被正常中止。
- 场景3:长耗时函数已经执行完成后才调用cancel:此时状态是FINISHED,cancel不会触发库的取消方法,不会产生无效调用。
注意:如果
cancelSomeLongRunningCancellableLibraryFunction对未启动/已结束的任务调用是幂等安全的,甚至可以省略状态判断直接调用,进一步简化逻辑,但用状态机过滤可以避免不必要的跨线程调用开销。
内容的提问来源于stack exchange,提问作者Steve M
相关产品推荐
相关产品推荐

