concurrency-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConcurrency 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 values MUST be awaited before scope ends. Forgetting to await silently cancels the task — no error, just missing data.
async let并发操作的数量在编译时是否已知?
├─ 是(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 陷阱:所有值必须在作用域结束前被await。忘记await会静默取消任务——不会报错,只会丢失数据。
async letTask 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 UIThe trap: inside runs on MainActor. For truly background work, use .
Task { }@MainActorTask.detached(priority:)新任务是否需要继承上下文?
├─ 是(继承优先级、actor、任务本地值)
│ └─ Task { }
│ 示例:在同一个actor上继续执行任务
│
└─ 否(完全独立执行)
└─ Task.detached { }
示例:不应阻塞UI的后台处理误区:在内部使用会在MainActor上运行。如果需要真正的后台任务,使用。
@MainActorTask { }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 stops execution immediately:
Task.cancel()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 to bypass safety without understanding implications:
nonisolatedswift
// ❌ 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) // 显式创建副本
}
}绝对不要在不了解影响的情况下使用绕过安全机制:
nonisolatedswift
// ❌ 危险——破坏了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 @MainActorNEVER 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
并发模式选择
| Pattern | Use When | Gotcha |
|---|---|---|
| 2-5 known parallel operations | Must await all bindings |
| Dynamic number of operations | Results arrive out of order |
| Fire-and-forget with context | Inherits actor isolation |
| True background work | No context inheritance |
| Shared mutable state | Reentrancy on suspension |
| 模式 | 使用场景 | 陷阱 |
|---|---|---|
| 2-5个已知的并行操作 | 必须await所有绑定 |
| 数量动态的操作 | 结果无序返回 |
| 带上下文的即发即弃任务 | 继承actor隔离 |
| 真正的后台任务 | 不继承上下文 |
| 共享可变状态 | 挂起时的可重入性 |
Sendable Quick Check
Sendable快速检查
| Type | Sendable? |
|---|---|
| Value types with Sendable properties | ✅ Implicit |
| ✅ 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
危险信号
| Smell | Problem | Fix |
|---|---|---|
| Losing structured concurrency | Use TaskGroup |
| Potential data race | Use actor or add locking |
| Data race | Remove nonisolated |
| Continuation without all-paths handling | Potential hang | Handle every code path |
| Losing priority/cancellation | Use structured |
| 代码异味 | 问题 | 修复方案 |
|---|---|---|
到处使用 | 失去结构化并发的优势 | 使用TaskGroup |
可变类添加 | 潜在数据竞争 | 改用actor或添加锁 |
| 数据竞争 | 移除nonisolated |
| 续体未处理所有分支 | 任务可能挂起 | 处理所有代码路径 |
所有任务都用 | 丢失优先级/取消机制 | 使用结构化的 |