Swift 并发任务的协作式取消
在 Swift 并发(Swift Concurrency)中,任务(Task)不会被强行终止,而是采用**协作式取消(cooperative cancellation)**机制。这意味着任务会被标记为“已取消”,但是否真正停止执行,取决于任务本身的逻辑处理。
本文将深入探讨任务取消的原理、如何正确使用它,以及如何编写高效的 Swift 并发代码。
一 什么是协作式取消?
协作式取消的核心思想是:调用方不能直接终止任务,而只能标记它为取消。任务本身需要定期检查这个标记,并决定要不要提前终止。
Swift 并不会强制中止任务,而是给你一个信号,告诉你“这个任务已经没用了,你要不要停下来?”你可以选择:
-
直接返回
-
提供部分结果
-
继续执行(如果业务逻辑需要)
二 任务取消的基本用法
来看一个 SwiftUI 代码示例。当用户输入搜索内容时,会触发异步搜索。
struct ContentView: View {
@State private var store = Store()
@State private var query = ""
var body: some View {
NavigationView {
List(store.results, id: \ .self) { result in
Text(result)
}
.searchable(text: $query)
.task(id: query) {
await store.search(matching: query)
}
}
}
}
task(id: query)
处理任务取消
这一行代码的作用是:
-
当
query
变化时,SwiftUI 启动一个新的搜索任务。 -
SwiftUI 会标记前一个任务为“已取消”,但不会立即终止它。
如果旧任务没有检查取消状态,它可能仍然会继续执行。这可能导致多个任务同时运行,影响性能。因此,我们需要手动处理任务取消。
三 在异步方法中正确处理取消
假设 Store
负责执行搜索查询,我们的 search(matching:)
方法可能如下:
@MainActor
final class Store: ObservableObject {
@Published private(set) var results: [String] = []
func search(matching query: String) async {
do {
let fetchedResults = await fetchData(query: query)
try Task.checkCancellation() // 检查任务是否已取消
results = fetchedResults
} catch {
results = []
}
}
}
Task.checkCancellation()
-
这个方法会抛出一个错误。
-
如果任务已被取消,它会立刻终止,后续代码不会执行。
-
这样可以避免执行无用的逻辑,比如过滤数据或更新 UI。
四 在多个步骤中检查任务取消
如果异步任务包含多个步骤,比如先获取数据,再处理数据,建议在多个关键点检查取消状态。
@MainActor
final class Store: ObservableObject {
@Published private(set) var results: [String] = []
func search(matching query: String) async {
do {
let rawData = await fetchRawData(query: query)
try Task.checkCancellation() // 第一次检查
let processedData = processData(rawData)
try Task.checkCancellation() // 第二次检查
results = processedData
} catch {
results = []
}
}
}
为什么要多次检查?
如果 fetchRawData
执行了一段时间,任务在这期间被取消,我们希望尽早停下来,而不是等整个任务执行完。
五 用 Task.isCancelled
进行检查
除了 Task.checkCancellation()
,Swift 还提供了 Task.isCancelled
这个属性。
actor SearchService {
private var cachedResults: [String] = []
func search(matching query: String) async -> [String] {
guard !Task.isCancelled else {
return cachedResults // 任务取消,直接返回缓存数据
}
let rawData = await fetchData(query: query)
guard !Task.isCancelled else {
return cachedResults // 避免不必要的计算
}
let filteredData = processData(rawData)
cachedResults = filteredData
return filteredData
}
}
Task.isCancelled
vs Task.checkCancellation()
方法 | 作用 |
---|---|
Task.checkCancellation() | 抛出错误,任务立即终止 |
Task.isCancelled | 返回 true/false ,可以手动决定是否终止 |
六 手动取消任务
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.search(matching: "Apple")
}
}
Button("取消任务") {
task?.cancel()
}
}
}
}
task?.cancel()
只会标记任务为取消,但不会真正终止它。因此,你仍然需要在 search(matching:)
里检查 Task.isCancelled
或 Task.checkCancellation()
。
七 结论
-
Swift 不会自动终止任务,只会标记它为取消。
-
用
Task.checkCancellation()
可以立即终止任务,防止执行不必要的逻辑。 -
用
Task.isCancelled
可以更灵活地处理任务取消。 -
如果任务有多个异步步骤,应该在关键点多次检查取消状态。
-
手动创建的任务需要手动取消,但仍然需要在任务内部检查取消状态。
这样,你就能写出高效、优雅的 Swift 并发代码,避免不必要的计算,提高用户体验!