如何用定时器取消Task对长耗时异步函数的等待?
解决Combine转Async/Await函数响应Task取消的问题
要让你的someLongRunningFunction()响应Task取消,核心是在Combine Publisher等待过程中监听Task的取消状态,一旦取消就终止Publisher并唤醒Continuation。以下是修改方案:
修改后的函数代码
func someLongRunningFunction() async throws -> Output { // 初始检查:如果Task已取消,直接抛出错误 try Task.checkCancellation() return try await withCheckedThrowingContinuation { continuation in var cancellable: AnyCancellable? // 创建监听Task取消的子任务 let cancellationObserver = Task { await Task.yield() // 等待当前Task进入取消状态 cancellable?.cancel() // 终止Combine Publisher continuation.resume(throwing: MyError.cancelled) // 唤醒Continuation,抛出取消错误 } cancellable = myPublisher .first() .sink(receiveCompletion: { completion in cancellationObserver.cancel() // 任务完成,取消监听任务避免内存泄漏 switch completion { case .finished: continuation.resume(throwing: MyError.cancelled) case let .failure(error): continuation.resume(throwing: error) } }, receiveValue: { value in cancellationObserver.cancel() // 获取到值后,取消监听任务 continuation.resume(with: .success(value)) }) } }
关键修改点说明
- 初始取消检查:用
Task.checkCancellation()替代原有的条件判断,符合Swift Async/Await的错误抛出规范,能在Task已取消时快速失败。 - 监听Task取消:新增
cancellationObserver子任务,专门监听当前Task的取消状态。当外部调用timedTask.cancel()时,该子任务会立即触发,取消Combine的订阅并唤醒Continuation,让异步函数停止等待并抛出取消错误。 - 清理监听任务:在Publisher的
sink回调中(无论成功、失败还是完成),都取消cancellationObserver,避免内存泄漏和后续无效操作。 - 简化逻辑:移除原有的
finishedWithoutValue变量,利用.first()的特性,当Publisher无值完成时直接抛出取消错误,逻辑更清晰。
调用示例(带1秒超时取消)
func executeWithTimeout() async { let timedTask = Task { do { let output = try await someLongRunningFunction() print("任务完成,结果:\(output)") } catch MyError.cancelled { print("任务被超时取消") } catch { print("任务执行失败:\(error)") } } // 1秒后取消任务 try? await Task.sleep(nanoseconds: 1_000_000_000) timedTask.cancel() // 等待任务处理完取消逻辑后继续执行 await timedTask.value print("后续代码继续执行") }
原代码失效原因
原代码仅在函数开头检查了一次Task取消状态,但当Task在进入withCheckedThrowingContinuation之后被取消时,没有任何机制监听这个取消事件,导致Combine Publisher继续运行,Continuation一直处于等待状态,最终timedTask.value会一直等待到Publisher完成,无法实现超时取消的需求。
内容的提问来源于stack exchange,提问作者Tometoyou
相关产品推荐
相关产品推荐

