2-27.【Concurrency】如何在 async 函数中优雅处理 Task cancellation?

1 阅读2分钟

1️⃣ 核心原则

Task cancellation 是协作式的。你不能强制杀掉任务,而是需要在 async 函数中主动检查取消状态并安全退出。
优雅处理的目标:

  1. 避免无用计算浪费 CPU
  2. 及时释放资源(文件、网络、内存)
  3. 不破坏结构化并发

2️⃣ 检查取消的基本方法

① 使用 Task.checkCancellation()

func doWork() async throws {
    for i in 0..<1000 {
        try Task.checkCancellation()  // 检查取消
        await doStep(i)
    }
}
  • 如果任务已取消 → 抛出 CancellationError
  • 异步调用者可捕获或让上层继续传播

② 使用 Task.isCancelled 手动判断

func doWork() async {
    for i in 0..<1000 {
        if Task.isCancelled {
            print("Task cancelled, exit early")
            return
        }
        await doStep(i)
    }
}
  • 不抛异常,只是提前返回
  • 更适合非 throwing 函数

3️⃣ 使用 withTaskCancellationHandler 做清理

  • 用于确保任务取消时可以执行资源释放操作
func doWork() async {
    await withTaskCancellationHandler {
        print("Cleaning up resources on cancellation")
    } operation: {
        for i in 0..<1000 {
            try? Task.checkCancellation()
            await doStep(i)
        }
    }
}
  • operation 执行任务主体
  • 取消时会执行 handler → 清理文件、网络连接等

4️⃣ 异步挂起点自然检查取消

  • 任何 await 都会自动检查任务取消状态:
func downloadFile() async throws -> Data {
    let data = try await URLSession.shared.data(from: url).0
    return data
}
  • 如果任务被取消 → await 会立即抛出 CancellationError
  • 你无需额外检查,除非在 CPU 密集循环中

5️⃣ CPU 密集型任务的处理技巧

  • CPU 密集循环不会自动响应取消 → 必须显式调用 Task.checkCancellation()
func heavyCompute() async throws -> Int {
    var sum = 0
    for i in 0..<1_000_000 {
        try Task.checkCancellation()   // 手动检查
        sum += i
    }
    return sum
}
  • 避免无用计算浪费 CPU
  • 保持应用响应性

6️⃣ 综合示例

func processItems(_ items: [Int]) async throws {
    try await withTaskCancellationHandler {
        print("Cleaning up resources...")
    } operation: {
        for item in items {
            try Task.checkCancellation()  // 检查取消
            await process(item)
        }
    }
}
  • 检查取消 → 协作式响应
  • withTaskCancellationHandler → 优雅释放资源
  • await → 挂起点自动响应取消

7️⃣ 面试必背总结

  1. Task cancellation 是协作式的,async 函数必须主动检查取消状态。

  2. 检查方式

    • Task.checkCancellation() → 抛 CancellationError
    • Task.isCancelled → 提前返回
  3. 资源释放withTaskCancellationHandler 在取消时执行清理

  4. await 自然挂起点会自动响应取消,但 CPU 密集循环需手动检查

  5. 优雅处理取消可提高应用响应性并防止资源泄漏