You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用定时器取消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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 07:13:31