Swift 协作式取消机制详解

Viewed 0

前言

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 启动新搜索任务并标记旧任务为取消。如果旧任务未检查取消状态,可能继续执行,因此需手动处理取消逻辑。

在异步方法中正确处理取消

假设 Storesearch(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() 中检查取消状态。

总结

  1. Swift 不自动终止任务,只标记取消状态。
  2. 使用 Task.checkCancellation() 立即终止任务。
  3. 使用 Task.isCancelled 灵活处理取消。
  4. 在多步骤任务中多次检查取消状态。
  5. 手动取消任务需配合取消检查。
    掌握这些技巧,能编写更高效、优雅的 Swift 并发代码,提升用户体验。
0 Answers