在 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 inText(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 = filteredDatareturn 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 并發代碼,避免不必要的計算,提高用戶體驗!