前言
Swift 并发提供了一种协作式取消机制,允许任务在需要时自行退出。简单来说,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 in
Text(verbatim: result)
}
.searchable(text: $query)
.task(id: query) {
await store.search(matching: 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 in
let title = food.metadata?["title"] as? String ?? ""
return title.localizedStandardContains(query)
}
} catch {
results = []
}
}
}
使用 Task.checkCancellation() 在关键点检查取消,若任务已取消则抛出错误,停止后续执行,避免不必要操作。
在多个步骤中检查取消状态
对于多步骤异步代码,应在多个关键点检查取消状态:
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 in
let title = food.metadata?["title"] as? String ?? ""
return title.localizedStandardContains(query)
}
} catch {
results = []
}
}
}
多次检查可确保任务在早期阶段被取消时及时停止,避免无效计算。
用 isCancelled 进行检查
除了 Task.checkCancellation(),还可使用 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 in
let title = food.metadata?["title"] as? String ?? ""
return title.localizedStandardContains(query)
}
return cachedResults
}
}
Task.checkCancellation() 在取消时抛出错误直接终止,而 Task.isCancelled 允许开发者决定是否返回部分结果。
手动取消任务
虽然 Swift 通常管理任务取消,但也可手动创建和取消任务:
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() 中检查取消状态。
总结
- Swift 不自动终止任务,只标记取消状态。
- 使用
Task.checkCancellation()立即终止任务。 - 使用
Task.isCancelled灵活处理取消。 - 在多步骤任务中多次检查取消状态。
- 手动取消任务需配合取消检查。
掌握这些技巧,能编写更高效、优雅的 Swift 并发代码,提升用户体验。