前言
Swift 并发提供了一种协作式取消(cooperative cancellation) 机制,来让任务在需要时自己退出。简单来说,Swift 不会强行终止你的任务,但它会告诉你任务已经被标记为取消,至于你要不要停下来,那是你自己的决定。
这篇文章会讲清楚任务取消的原理、如何正确使用它,以及如何写出高效又优雅的代码。
什么是协作式取消?
协作式取消的核心思想是:
- 调用方(比如 SwiftUI)没法直接终止任务,只能“标记”为取消。
- 任务本身需要定期检查这个标记,并决定要不要提前终止。
- 你可以选择直接返回、提供部分结果,或者继续执行,全看你的业务逻辑。
简单来说,Swift 只是给你一个“信号”——“嘿,这个任务已经没用了,看看你要不要停下来”。
如何用 Task API 处理任务取消
来看个例子,这是一个 SwiftUI 界面,用户输入搜索内容时,会触发异步搜索。
这里最关键的是 task(id: query) 这行代码:
- 当
query
变化时,SwiftUI 会启动一个新的搜索任务。 - 同时,它会标记上一个任务为“已取消”,但不会立刻终止它。
- 如果旧任务里没有检查取消状态,它可能仍然会跑完所有逻辑。
这意味着,如果用户输入了很多字符,可能会同时存在多个搜索任务,这就是为什么我们要手动处理取消逻辑。
在异步方法中正确处理取消
假设 Store 负责查询数据,我们的 search(matching:) 方法如下:
这里有个关键点:**Task.checkCancellation()**。
- 这个方法会抛出一个错误,如果任务已经被取消,它就会立刻停止执行,后续的代码不会再运行。
- 这样可以避免执行一些不必要的逻辑,比如过滤数据、更新 UI 等。
- 如果任务被取消,我们直接把 results 置空,这样用户不会看到过时的搜索结果。
在多个步骤中检查取消状态
如果你的异步代码有多个步骤,比如先获取数据、然后再做一些处理,那你可能需要在多个关键点检查任务是否已取消,否则即使任务已经无效了,它可能还会跑完整个流程。
为什么要多次检查?
- 如果 foodQuery 运行了一段时间,任务被取消了,我们希望尽早停下来,而不是等所有代码都跑完。
- 某些任务可能是分步执行的,比如先获取原始数据,再处理数据。如果第一步完成了,但任务已经取消了,我们就没必要继续处理数据。
用 isCancelled 进行检查
除了 Task.checkCancellation() 之外,Swift 还提供了 Task.isCancelled 这个属性,它是一个布尔值,你可以用它更灵活地处理任务取消:
两种方式的区别:
- Task.checkCancellation():如果任务已取消,直接抛出错误,代码不再继续执行。
- Task.isCancelled:任务是否继续执行,由你自己决定,比如可以提前返回缓存数据,而不是直接终止。
手动取消任务
通常情况下,Swift 会帮你管理任务的取消,但如果你想手动创建和取消任务,也可以用 Task:
这里 task?.cancel() 只会标记任务为取消,但不会真的终止它,所以你仍然需要在 fetch() 里检查 Task.isCancelled 或 Task.checkCancellation()。
总结
- Swift 不会自动终止任务,只会标记它为取消。
- 用 Task.checkCancellation() 可以立即终止任务,防止执行不必要的逻辑。
- 用 Task.isCancelled 可以更灵活地决定如何处理取消。
- 如果任务有多个异步步骤,应该在关键点多次检查取消状态。
- 手动创建的任务可以用 .cancel() 取消,但仍然需要手动检查取消状态。
学会这些,你的 Swift 并发代码就能更高效、更优雅地处理任务取消,让用户体验更流畅!