Swift 并发提供了一种协作式取消(cooperative cancellation) 机制,来让任务在需要时自己退出。简单来说,Swift 不会强行终止你的任务,但它会告诉你任务已经被标记为取消,至于你要不要停下来,那是你自己的决定。
这篇文章会讲清楚任务取消的原理、如何正确使用它,以及如何写出高效又优雅的代码。
协作式取消的核心思想是:
简单来说,Swift 只是给你一个“信号”——“嘿,这个任务已经没用了,看看你要不要停下来”。
来看个例子,这是一个 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)
}
}
}
}
这里最关键的是 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 in
let title = food.metadata?["title"] as? String ?? ""
return title.localizedStandardContains(query)
}
} catch {
results = []
}
}
}
这里有个关键点:Task.checkCancellation()
。
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 in
let title = food.metadata?["title"] as? String ?? ""
return title.localizedStandardContains(query)
}
} catch {
results = []
}
}
}
为什么要多次检查?
foodQuery
运行了一段时间,任务被取消了,我们希望尽早停下来,而不是等所有代码都跑完。 除了 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 in
let 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()
。
Task.checkCancellation()
可以立即终止任务,防止执行不必要的逻辑。 Task.isCancelled
可以更灵活地决定如何处理取消。 .cancel()
取消,但仍然需要手动检查取消状态。 学会这些,你的 Swift 并发代码就能更高效、更优雅地处理任务取消,让用户体验更流畅!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。