concurrency-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Concurrency Patterns — Expert Decisions

并发模式——进阶决策指南

Expert decision frameworks for Swift concurrency choices. Claude knows async/await syntax — this skill provides judgment calls for pattern selection and isolation boundaries.

Swift并发选择的进阶决策框架。Claude熟悉async/await语法——本技能提供模式选择与隔离边界的判断依据。

Decision Trees

决策树

async let vs TaskGroup

async let vs TaskGroup

Is the number of concurrent operations known at compile time?
├─ YES (2-5 fixed operations)
│  └─ async let
│     async let user = fetchUser()
│     async let posts = fetchPosts()
│     let (user, posts) = await (try user, try posts)
└─ NO (dynamic count, array of IDs)
   └─ TaskGroup
      try await withThrowingTaskGroup(of: User.self) { group in
          for id in userIds { group.addTask { ... } }
      }
async let gotcha: All
async let
values MUST be awaited before scope ends. Forgetting to await silently cancels the task — no error, just missing data.
并发操作的数量在编译时是否已知?
├─ 是(2-5个固定操作)
│  └─ async let
│     async let user = fetchUser()
│     async let posts = fetchPosts()
│     let (user, posts) = await (try user, try posts)
└─ 否(数量动态,如ID数组)
   └─ TaskGroup
      try await withThrowingTaskGroup(of: User.self) { group in
          for id in userIds { group.addTask { ... } }
      }
async let 陷阱:所有
async let
值必须在作用域结束前被await。忘记await会静默取消任务——不会报错,只会丢失数据。

Task vs Task.detached

Task vs Task.detached

Does the new task need to inherit context?
├─ YES (inherit priority, actor, task-locals)
│  └─ Task { }
│     Example: Continue work on same actor
└─ NO (fully independent execution)
   └─ Task.detached { }
      Example: Background processing that shouldn't block UI
The trap:
Task { }
inside
@MainActor
runs on MainActor. For truly background work, use
Task.detached(priority:)
.
新任务是否需要继承上下文?
├─ 是(继承优先级、actor、任务本地值)
│  └─ Task { }
│     示例:在同一个actor上继续执行任务
└─ 否(完全独立执行)
   └─ Task.detached { }
      示例:不应阻塞UI的后台处理
误区:在
@MainActor
内部使用
Task { }
会在MainActor上运行。如果需要真正的后台任务,使用
Task.detached(priority:)

Actor vs Class with Lock

Actor vs 带锁的Class

Is the mutable state accessed from async contexts?
├─ YES → Actor (compiler-enforced isolation)
└─ NO → Is it performance-critical?
   ├─ YES → Class with lock (less overhead)
   │  └─ Consider @unchecked Sendable if crossing boundaries
   └─ NO → Actor (safer, cleaner)
When actors lose: High-contention scenarios where lock granularity matters. Actor methods are fully isolated — can't lock just part of the state.
可变状态是否会在异步上下文中被访问?
├─ 是 → Actor(编译器强制隔离)
└─ 否 → 是否对性能要求极高?
   ├─ 是 → 带锁的Class(开销更低)
   │  └─ 如果跨边界使用,考虑添加@unchecked Sendable
   └─ 否 → Actor(更安全、更简洁)
Actor的劣势:在高竞争场景下,锁的粒度很重要。Actor的方法是完全隔离的——无法只锁定部分状态。

Sendable Conformance

Sendable 合规性

Is the type crossing concurrency boundaries?
├─ NO → Don't add Sendable
└─ YES → What kind of type?
   ├─ Struct with only Sendable properties
   │  └─ Implicit Sendable (or add explicit)
   ├─ Class with immutable state
   │  └─ Add Sendable, make let-only
   ├─ Class with mutable state
   │  └─ Is it manually thread-safe?
   │     ├─ YES → @unchecked Sendable
   │     └─ NO → Convert to actor
   └─ Closure
      └─ Mark @Sendable, capture only Sendable values

类型是否会跨越并发边界?
├─ 否 → 无需添加Sendable
└─ 是 → 是什么类型?
   ├─ 仅包含Sendable属性的结构体
   │  └─ 隐式符合Sendable(或显式添加)
   ├─ 状态不可变的类
   │  └─ 添加Sendable合规性,确保所有属性为let
   ├─ 状态可变的类
   │  └─ 是否是手动线程安全的?
   │     ├─ 是 → @unchecked Sendable
   │     └─ 否 → 转换为Actor
   └─ 闭包
      └─ 标记为@Sendable,仅捕获Sendable类型的值

NEVER Do

绝对禁忌

Task & Structured Concurrency

任务与结构化并发

NEVER create unstructured tasks for parallel work that should be grouped:
swift
// ❌ No way to wait for completion, handle errors, or cancel
func loadData() async {
    Task { try? await fetchUsers() }
    Task { try? await fetchPosts() }
    // Returns immediately, tasks orphaned
}

// ✅ Structured — errors propagate, cancellation works
func loadData() async throws {
    try await withThrowingTaskGroup(of: Void.self) { group in
        group.addTask { try await fetchUsers() }
        group.addTask { try await fetchPosts() }
    }
}
NEVER assume
Task.cancel()
stops execution immediately:
swift
// ❌ Assumes cancellation is synchronous
task.cancel()
let result = task.value  // Task may still be running!

// ✅ Cancellation is cooperative — code must check
func longOperation() async throws {
    for item in items {
        try Task.checkCancellation()  // Or check Task.isCancelled
        await process(item)
    }
}
NEVER forget that async let bindings auto-cancel if not awaited:
swift
// ❌ profileImage is SILENTLY CANCELLED
func loadUser() async throws -> User {
    async let user = fetchUser()
    async let profileImage = fetchImage()  // Never awaited!
    return try await user  // profileImage cancelled, no error
}

// ✅ Await all async let bindings
func loadUser() async throws -> (User, UIImage?) {
    async let user = fetchUser()
    async let profileImage = fetchImage()
    return try await (user, profileImage)  // Both awaited
}
绝对不要为应该分组的并行工作创建非结构化任务:
swift
// ❌ 无法等待完成、处理错误或取消任务
func loadData() async {
    Task { try? await fetchUsers() }
    Task { try? await fetchPosts() }
    // 立即返回,任务成为孤儿
}

// ✅ 结构化——错误会传播,取消机制生效
func loadData() async throws {
    try await withThrowingTaskGroup(of: Void.self) { group in
        group.addTask { try await fetchUsers() }
        group.addTask { try await fetchPosts() }
    }
}
绝对不要假设
Task.cancel()
会立即停止执行:
swift
// ❌ 假设取消是同步的
task.cancel()
let result = task.value  // 任务可能仍在运行!

// ✅ 取消是协作式的——代码必须主动检查
func longOperation() async throws {
    for item in items {
        try Task.checkCancellation()  // 或者检查Task.isCancelled
        await process(item)
    }
}
绝对不要忘记如果不await,async let绑定会自动取消:
swift
// ❌ profileImage 被静默取消
func loadUser() async throws -> User {
    async let user = fetchUser()
    async let profileImage = fetchImage()  // 从未被await!
    return try await user  // profileImage被取消,无错误提示
}

// ✅ await所有async let绑定
func loadUser() async throws -> (User, UIImage?) {
    async let user = fetchUser()
    async let profileImage = fetchImage()
    return try await (user, profileImage)  // 两者都被await
}

Actor Isolation

Actor隔离

NEVER ignore actor reentrancy:
swift
// ❌ State can change during suspension
actor BankAccount {
    var balance: Double = 100

    func transferAll() async throws {
        let amount = balance  // Capture balance
        try await sendMoney(amount)  // Suspension point!
        balance = 0  // Balance might have changed since capture!
    }
}

// ✅ Check state AFTER suspension
actor BankAccount {
    var balance: Double = 100

    func transferAll() async throws {
        let amount = balance
        try await sendMoney(amount)
        // Re-check or use atomic operation
        guard balance >= amount else {
            throw BankError.balanceChanged
        }
        balance -= amount
    }
}
NEVER expose actor state as reference types:
swift
// ❌ Array reference escapes actor isolation
actor Cache {
    var items: [Item] = []

    func getItems() -> [Item] {
        items  // Returns reference that can be mutated outside!
    }
}

// ✅ Return copy or use value types
actor Cache {
    private var items: [Item] = []

    func getItems() -> [Item] {
        Array(items)  // Explicit copy
    }
}
NEVER use
nonisolated
to bypass safety without understanding implications:
swift
// ❌ Dangerous — defeats actor protection
actor DataManager {
    var cache: [String: Data] = [:]

    nonisolated func unsafeAccess() -> [String: Data] {
        cache  // DATA RACE — accessing actor state without isolation!
    }
}

// ✅ nonisolated only for immutable or independent state
actor DataManager {
    let id = UUID()  // Immutable — safe

    nonisolated var identifier: String {
        id.uuidString  // Safe — accessing immutable state
    }
}
绝对不要忽略Actor的可重入性:
swift
// ❌ 挂起期间状态可能发生变化
actor BankAccount {
    var balance: Double = 100

    func transferAll() async throws {
        let amount = balance  // 捕获当前余额
        try await sendMoney(amount)  // 挂起点!
        balance = 0  // 自捕获余额后,实际余额可能已变化!
    }
}

// ✅ 挂起后重新检查状态
actor BankAccount {
    var balance: Double = 100

    func transferAll() async throws {
        let amount = balance
        try await sendMoney(amount)
        // 重新检查或使用原子操作
        guard balance >= amount else {
            throw BankError.balanceChanged
        }
        balance -= amount
    }
}
绝对不要将Actor的状态以引用类型暴露:
swift
// ❌ 数组引用逃脱了Actor的隔离
actor Cache {
    var items: [Item] = []

    func getItems() -> [Item] {
        items  // 返回的引用可在外部被修改!
    }
}

// ✅ 返回副本或使用值类型
actor Cache {
    private var items: [Item] = []

    func getItems() -> [Item] {
        Array(items)  // 显式创建副本
    }
}
绝对不要在不了解影响的情况下使用
nonisolated
绕过安全机制:
swift
// ❌ 危险——破坏了Actor的保护
actor DataManager {
    var cache: [String: Data] = [:]

    nonisolated func unsafeAccess() -> [String: Data] {
        cache  // 数据竞争——未通过隔离访问Actor状态!
    }
}

// ✅ nonisolated仅用于不可变或独立状态
actor DataManager {
    let id = UUID()  // 不可变——安全

    nonisolated var identifier: String {
        id.uuidString  // 安全——访问不可变状态
    }
}

@MainActor

@MainActor

NEVER access @Published from background without MainActor:
swift
// ❌ Undefined behavior — may crash, may corrupt
Task.detached {
    viewModel.isLoading = false  // Background thread!
}

// ✅ Explicit MainActor
Task { @MainActor in
    viewModel.isLoading = false
}
// Or mark entire ViewModel as @MainActor
NEVER block MainActor with synchronous work:
swift
// ❌ UI freezes during heavy computation
@MainActor
func processData() {
    let result = heavyComputation(data)  // Blocks UI!
    display(result)
}

// ✅ Offload to detached task
@MainActor
func processData() async {
    let result = await Task.detached {
        heavyComputation(data)
    }.value
    display(result)  // Back on MainActor
}
绝对不要在后台线程不通过MainActor访问@Published属性:
swift
// ❌ 未定义行为——可能崩溃或数据损坏
Task.detached {
    viewModel.isLoading = false  // 在后台线程执行!
}

// ✅ 显式使用MainActor
Task { @MainActor in
    viewModel.isLoading = false
}
// 或者将整个ViewModel标记为@MainActor
绝对不要用同步工作阻塞MainActor:
swift
// ❌ 繁重计算期间UI会冻结
@MainActor
func processData() {
    let result = heavyComputation(data)  // 阻塞UI!
    display(result)
}

// ✅ 卸载到detached任务
@MainActor
func processData() async {
    let result = await Task.detached {
        heavyComputation(data)
    }.value
    display(result)  // 回到MainActor执行
}

Continuations

续体(Continuation)

NEVER resume continuation more than once:
swift
// ❌ CRASHES — continuation resumed twice
func fetchAsync() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        fetch { result in
            continuation.resume(returning: result)
        }
        fetch { result in  // Oops, called again!
            continuation.resume(returning: result)  // CRASH!
        }
    }
}

// ✅ Ensure exactly-once resumption
func fetchAsync() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        var hasResumed = false
        fetch { result in
            guard !hasResumed else { return }
            hasResumed = true
            continuation.resume(returning: result)
        }
    }
}
NEVER forget to resume continuation:
swift
// ❌ Task hangs forever if error path doesn't resume
func fetchAsync() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        fetch { data, error in
            if let data = data {
                continuation.resume(returning: data)
            }
            // Missing else! Continuation never resumed if error
        }
    }
}

// ✅ Handle all paths
func fetchAsync() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        fetch { data, error in
            if let error = error {
                continuation.resume(throwing: error)
            } else if let data = data {
                continuation.resume(returning: data)
            } else {
                continuation.resume(throwing: FetchError.noData)
            }
        }
    }
}

绝对不要多次恢复续体:
swift
// ❌ 崩溃——续体被恢复两次
func fetchAsync() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        fetch { result in
            continuation.resume(returning: result)
        }
        fetch { result in  // 哦,又调用了一次!
            continuation.resume(returning: result)  // 崩溃!
        }
    }
}

// ✅ 确保仅恢复一次
func fetchAsync() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        var hasResumed = false
        fetch { result in
            guard !hasResumed else { return }
            hasResumed = true
            continuation.resume(returning: result)
        }
    }
}
绝对不要忘记恢复续体:
swift
// ❌ 如果走错误路径,任务会永久挂起
func fetchAsync() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        fetch { data, error in
            if let data = data {
                continuation.resume(returning: data)
            }
            // 缺少else分支!如果出错,续体永远不会被恢复
        }
    }
}

// ✅ 处理所有分支
func fetchAsync() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        fetch { data, error in
            if let error = error {
                continuation.resume(throwing: error)
            } else if let data = data {
                continuation.resume(returning: data)
            } else {
                continuation.resume(throwing: FetchError.noData)
            }
        }
    }
}

Essential Patterns

核心模式

Task-Local Values

任务本地值

swift
enum RequestContext {
    @TaskLocal static var requestId: String?
    @TaskLocal static var userId: String?
}

// Set context for entire task tree
func handleRequest() async {
    await RequestContext.$requestId.withValue(UUID().uuidString) {
        await RequestContext.$userId.withValue(currentUserId) {
            await processRequest()  // All child tasks inherit context
        }
    }
}

// Access anywhere in task tree
func logEvent(_ message: String) {
    let requestId = RequestContext.requestId ?? "unknown"
    logger.info("[\(requestId)] \(message)")
}
swift
enum RequestContext {
    @TaskLocal static var requestId: String?
    @TaskLocal static var userId: String?
}

// 为整个任务树设置上下文
func handleRequest() async {
    await RequestContext.$requestId.withValue(UUID().uuidString) {
        await RequestContext.$userId.withValue(currentUserId) {
            await processRequest()  // 所有子任务都会继承上下文
        }
    }
}

// 在任务树的任意位置访问上下文
func logEvent(_ message: String) {
    let requestId = RequestContext.requestId ?? "unknown"
    logger.info("[\(requestId)] \(message)")
}

Cancellation-Aware Loops

支持取消的循环

swift
func processItems(_ items: [Item]) async throws {
    for item in items {
        // Check at start of each iteration
        try Task.checkCancellation()

        // Or handle gracefully without throwing
        guard !Task.isCancelled else {
            await saveProgress(items: processedItems)
            return
        }

        await process(item)
    }
}
swift
func processItems(_ items: [Item]) async throws {
    for item in items {
        // 在每次迭代开始时检查
        try Task.checkCancellation()

        // 或者优雅处理而不抛出错误
        guard !Task.isCancelled else {
            await saveProgress(items: processedItems)
            return
        }

        await process(item)
    }
}

AsyncStream from Delegate

从代理创建AsyncStream

swift
func locationUpdates() -> AsyncStream<CLLocation> {
    AsyncStream { continuation in
        let delegate = LocationDelegate { location in
            continuation.yield(location)
        }

        continuation.onTermination = { @Sendable _ in
            delegate.stop()
        }

        delegate.start()
    }
}

swift
func locationUpdates() -> AsyncStream<CLLocation> {
    AsyncStream { continuation in
        let delegate = LocationDelegate { location in
            continuation.yield(location)
        }

        continuation.onTermination = { @Sendable _ in
            delegate.stop()
        }

        delegate.start()
    }
}

Quick Reference

快速参考

Concurrency Pattern Selection

并发模式选择

PatternUse WhenGotcha
async let
2-5 known parallel operationsMust await all bindings
TaskGroup
Dynamic number of operationsResults arrive out of order
Task { }
Fire-and-forget with contextInherits actor isolation
Task.detached
True background workNo context inheritance
actor
Shared mutable stateReentrancy on suspension
模式使用场景陷阱
async let
2-5个已知的并行操作必须await所有绑定
TaskGroup
数量动态的操作结果无序返回
Task { }
带上下文的即发即弃任务继承actor隔离
Task.detached
真正的后台任务不继承上下文
actor
共享可变状态挂起时的可重入性

Sendable Quick Check

Sendable快速检查

TypeSendable?
Value types with Sendable properties✅ Implicit
let
-only classes
✅ Add conformance
Mutable classes with internal locking⚠️ @unchecked Sendable
Mutable classes without locking❌ Use actor instead
Closures✅ If marked @Sendable
类型是否符合Sendable?
仅包含Sendable属性的值类型✅ 隐式符合
所有属性为let的类✅ 添加合规性
内部带锁的可变类⚠️ @unchecked Sendable
无锁的可变类❌ 改用actor
闭包✅ 如果标记为@Sendable

Red Flags

危险信号

SmellProblemFix
Task { }
everywhere
Losing structured concurrencyUse TaskGroup
@unchecked Sendable
on mutable class
Potential data raceUse actor or add locking
nonisolated
accessing mutable state
Data raceRemove nonisolated
Continuation without all-paths handlingPotential hangHandle every code path
Task.detached
for everything
Losing priority/cancellationUse structured
Task { }
代码异味问题修复方案
到处使用
Task { }
失去结构化并发的优势使用TaskGroup
可变类添加
@unchecked Sendable
潜在数据竞争改用actor或添加锁
nonisolated
访问可变状态
数据竞争移除nonisolated
续体未处理所有分支任务可能挂起处理所有代码路径
所有任务都用
Task.detached
丢失优先级/取消机制使用结构化的
Task { }