大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
- 前言
- 什么是协作式取消?
- 如何用 Task API 处理任务取消
- 在异步方法中正确处理取消
- 在多个步骤中检查取消状态
- 用 isCancelled 进行检查
- 手动取消任务
- 总结
前言
Swift 并发提供了一种协作式取消(cooperative cancellation) 机制,来让任务在需要时自己退出。简单来说,Swift 不会强行终止你的任务,但它会告诉你任务已经被标记为取消,至于你要不要停下来,那是你自己的决定。
这篇文章会讲清楚任务取消的原理、如何正确使用它,以及如何写出高效又优雅的代码。
什么是协作式取消?
协作式取消的核心思想是:
- 调用方(比如 SwiftUI)没法直接终止任务,只能“标记”为取消。
- 任务本身需要定期检查这个标记,并决定要不要提前终止。
- 你可以选择直接返回、提供部分结果,或者继续执行,全看你的业务逻辑。
简单来说,Swift 只是给你一个“信号”——“嘿,这个任务已经没用了,看看你要不要停下来”。
如何用 Task API 处理任务取消
来看个例子,这是一个 SwiftUI 界面,用户输入搜索内容时,会触发异步搜索。
struct ContentView: View {@State private var store = Store()@State private var query = ""var body: some View {NavigationStack {List(store.results, id: \.self) { result inText(verbatim: result)}.searchable(text: $query).task(id: query) {await store.search(matching: query)}}}
}
这里最关键的是 task(id: query)
这行代码:
- 当
query
变化时,SwiftUI 会启动一个新的搜索任务。 - 同时,它会标记上一个任务为“已取消”,但不会立刻终止它。
- 如果旧任务里没有检查取消状态,它可能仍然会跑完所有逻辑。
这意味着,如果用户输入了很多字符,可能会同时存在多个搜索任务,这就是为什么我们要手动处理取消逻辑。
在异步方法中正确处理取消
假设 Store
负责查询数据,我们的 search(matching:)
方法如下:
import HealthKit@MainActor @Observable final class Store {private(set) var results: [HKCorrelation] = []private let store = HKHealthStore()func search(matching query: String) async {let foodQuery = HKSampleQueryDescriptor(predicates: [.correlation(type: .init(.food))],sortDescriptors: [])do {let food = try await foodQuery.result(for: store)try Task.checkCancellation() // 检查任务是否已取消results = food.filter { food inlet title = food.metadata?["title"] as? String ?? ""return title.localizedStandardContains(query)}} catch {results = []}}
}
这里有个关键点:Task.checkCancellation()
。
- 这个方法会抛出一个错误,如果任务已经被取消,它就会立刻停止执行,后续的代码不会再运行。
- 这样可以避免执行一些不必要的逻辑,比如过滤数据、更新 UI 等。
- 如果任务被取消,我们直接把
results
置空,这样用户不会看到过时的搜索结果。
在多个步骤中检查取消状态
如果你的异步代码有多个步骤,比如先获取数据、然后再做一些处理,那你可能需要在多个关键点检查任务是否已取消,否则即使任务已经无效了,它可能还会跑完整个流程。
import HealthKit@MainActor @Observable final class Store {private(set) var results: [HKCorrelation] = []private let store = HKHealthStore()func search(matching query: String) async {let foodQuery = HKSampleQueryDescriptor(predicates: [.correlation(type: .init(.food))],sortDescriptors: [])do {let food = try await foodQuery.result(for: store)try Task.checkCancellation() // 第一次取消检查// 假设这里有额外的数据处理try Task.checkCancellation() // 第二次取消检查results = food.filter { food inlet title = food.metadata?["title"] as? String ?? ""return title.localizedStandardContains(query)}} catch {results = []}}
}
为什么要多次检查?
- 如果
foodQuery
运行了一段时间,任务被取消了,我们希望尽早停下来,而不是等所有代码都跑完。 - 某些任务可能是分步执行的,比如先获取原始数据,再处理数据。如果第一步完成了,但任务已经取消了,我们就没必要继续处理数据。
用 isCancelled 进行检查
除了 Task.checkCancellation()
之外,Swift 还提供了 Task.isCancelled
这个属性,它是一个布尔值,你可以用它更灵活地处理任务取消:
actor SearchService {private var cachedResults: [HKCorrelation] = []private let store = HKHealthStore()func search(matching query: String) async throws -> [HKCorrelation] {guard !Task.isCancelled else {return cachedResults // 任务取消了,直接返回缓存}let foodQuery = HKSampleQueryDescriptor(predicates: [.correlation(type: .init(.food))],sortDescriptors: [])let food = try await foodQuery.result(for: store)guard !Task.isCancelled else {return cachedResults // 任务取消了,避免不必要的计算}cachedResults = food.filter { food inlet title = food.metadata?["title"] as? String ?? ""return title.localizedStandardContains(query)}return cachedResults}
}
两种方式的区别:
Task.checkCancellation()
:如果任务已取消,直接抛出错误,代码不再继续执行。Task.isCancelled
:任务是否继续执行,由你自己决定,比如可以提前返回缓存数据,而不是直接终止。
手动取消任务
通常情况下,Swift 会帮你管理任务的取消,但如果你想手动创建和取消任务,也可以用 Task
:
struct ExampleView: View {@State private var store = Store()@State private var task: Task<Void, Never>?var body: some View {VStack {Button("开始任务") {task = Task {await store.fetch()}}Button("取消任务") {task?.cancel()}}}
}
这里 task?.cancel()
只会标记任务为取消,但不会真的终止它,所以你仍然需要在 fetch()
里检查 Task.isCancelled
或 Task.checkCancellation()
。
总结
- Swift 不会自动终止任务,只会标记它为取消。
- 用
Task.checkCancellation()
可以立即终止任务,防止执行不必要的逻辑。 - 用
Task.isCancelled
可以更灵活地决定如何处理取消。 - 如果任务有多个异步步骤,应该在关键点多次检查取消状态。
- 手动创建的任务可以用
.cancel()
取消,但仍然需要手动检查取消状态。
学会这些,你的 Swift 并发代码就能更高效、更优雅地处理任务取消,让用户体验更流畅!