攻克Swift并发难题:ConcurrencyRecipes实用解决方案全解析
你是否还在为Swift Concurrency中的结构化并发、Actor隔离和异步上下文转换而头疼?面对非Sendable类型传递、任务取消处理和优先级传播等问题时束手无策?本文将带你深入探索ConcurrencyRecipes项目提供的实战解决方案,掌握从基础到高级的Swift并发编程技巧,让你的异步代码更安全、高效且易于维护。
读完本文后,你将能够:
- 理解Swift Concurrency的核心挑战及解决方案
- 正确实现结构化并发模式处理任务依赖
- 掌握Actor隔离域间数据安全传递技巧
- 优雅处理异步上下文转换与任务取消
- 构建线程安全的异步缓存机制
项目概述:ConcurrencyRecipes是什么?
ConcurrencyRecipes是一个专注于解决Swift Concurrency实际问题的开源项目,提供了一系列经过验证的设计模式和代码实现。项目基于Swift 5.5+的并发特性,针对开发者在实际开发中遇到的常见挑战,如结构化并发管理、Actor隔离、异步上下文转换等提供实用解决方案。
项目结构采用模块化设计,核心代码组织如下:
ConcurrencyRecipes/
├── Sources/ # 核心源代码
│ ├── ConcurrencyRecipes/ # 主模块
│ └── PreconcurrencyLib/ # 预处理并发库
├── Recipes/ # 解决方案文档
│ ├── Structured.md # 结构化并发方案
│ ├── Isolation.md # 隔离域处理方案
│ ├── AsyncContext.md # 异步上下文方案
│ └── ...
└── Tests/ # 单元测试
核心解决方案解析
1. 结构化并发:任务管理与依赖处理
延迟异步值计算与缓存
在并发环境中,我们经常需要延迟计算某个昂贵的异步值并缓存结果。直接实现可能会导致Actor重入问题,如下错误示例所示:
actor MyActor {
private var expensiveValue: Int?
private func makeValue() async -> Int {
0 // 模拟昂贵计算
}
public var value: Int {
get async {
if let value = expensiveValue {
return value
}
// 危险1:Actor重入问题
let value = await makeValue()
self.expensiveValue = value
return value
}
}
}
解决方案1:使用非结构化Task
将异步计算移至Task中并缓存Task本身,虽然简单但失去了优先级传播和取消支持:
actor MyActor {
private var expensiveValueTask: Task<Int, Never>?
private func makeValue() async -> Int {
0 // 模拟昂贵计算
}
public var value: Int {
get async {
if let task = expensiveValueTask {
return await task.value
}
let task = Task { await makeValue() }
self.expensiveValueTask = task
return await task.value
}
}
}
解决方案2:跟踪Continuation(结构化并发方式)
使用CheckedContinuation实现完全结构化的并发方案,支持优先级传播:
actor MyActor {
typealias ValueContinuation = CheckedContinuation<Int, Never>
enum State {
case empty
case pending([ValueContinuation])
case filled(Int)
}
private var state = State.empty
private func makeValue() async -> Int {
0 // 模拟昂贵计算
}
private func fill(with result: Int) {
guard case let .pending(array) = state else { fatalError() }
for continuation in array {
continuation.resume(returning: result)
}
self.state = .filled(result)
}
private func trackContinuation(_ continuation: ValueContinuation) {
guard case var .pending(array) = state else { fatalError() }
array.append(continuation)
self.state = .pending(array)
}
public var value: Int {
get async {
switch state {
case let .filled(value):
return value
case .pending:
return await withCheckedContinuation { continuation in
trackContinuation(continuation)
}
case .empty:
self.state = .pending([])
let value = await makeValue()
fill(with: value)
return value
}
}
}
}
解决方案3:支持取消的Continuation跟踪
更复杂但功能完善的实现,增加了取消支持:
actor AsyncCache<Value> where Value: Sendable {
typealias ValueContinuation = CheckedContinuation<Value, Error>
typealias ValueResult = Result<Value, Error>
enum State {
case empty
case pending([UUID: ValueContinuation])
case filled(ValueResult)
}
private var state = State.empty
private let valueProvider: () async throws -> Value
init(valueProvider: @escaping () async throws -> Value) {
self.valueProvider = valueProvider
}
// ... 完整实现参见项目代码 ...
public var value: Value {
get async throws {
switch state {
case let .filled(value):
return try value.get()
case .pending:
let id = UUID()
return try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
trackContinuation(continuation, with: id)
}
} onCancel: {
Task { await self.cancelContinuation(with: id) }
}
case .empty:
self.state = .pending([:])
do {
let value = try await makeValue()
fill(with: .success(value))
return value
} catch {
fill(with: .failure(error))
throw error
}
}
}
}
}
2. 隔离域处理:跨Actor数据传递与访问
非Sendable类型的安全传递
当需要将非Sendable类型传递到不同隔离域的函数时,直接传递会产生编译警告:
func myAsyncFunction(_ nonSendable: NonSendable) async {
}
let nonSendable = NonSendable()
await myAsyncFunction(nonSendable) // 产生警告
解决方案:使用@Sendable闭包延迟创建
通过在目标隔离域中创建非Sendable实例,避免跨域传递:
func myAsyncFunction(_ nonSendable: @Sendable () -> NonSendable) async {
let value = nonSendable()
// 使用创建的实例
}
await myAsyncFunction({ NonSendable() })
动态Actor隔离管理
在不同使用场景中需要动态改变隔离域时,可以使用以下模式:
方案1:使用assumeIsolated
func takesClosure(_ block: () -> Void) {
}
takesClosure {
MainActor.assumeIsolated {
accessMainActorOnlyThing()
}
}
方案2:创建特定Actor的包装函数
func takesMainActorClosure(_ block: @MainActor () -> Void) {
takesClosure {
MainActor.assumeIsolated {
block()
}
}
}
takesMainActorClosure {
accessMainActorOnlyThing()
}
方案3:使用isolated参数
func takesClosure(isolatedTo actor: isolated any Actor, block: () -> Void) {
// 在指定的Actor隔离域中执行block
}
自定义全局Actor
对于需要集中管理的全局状态,可以创建自定义全局Actor:
@globalActor
public actor CustomGlobalActor {
public static let shared = CustomGlobalActor()
public static func assumeIsolated<T>(_ operation: @CustomGlobalActor () throws -> T,
file: StaticString = #fileID,
line: UInt = #line) rethrows -> T {
Self.shared.assertIsolated()
return try withoutActuallyEscaping(operation) { fn in
try unsafeBitCast(fn, to: (() throws -> T).self)()
}
}
}
3. 异步上下文转换:同步到异步的桥梁
从同步上下文调用异步函数
在同步上下文中调用异步函数是常见需求,但直接使用可能导致 ordering 和错误处理问题:
基础方案:非结构化Task
func work() async throws {
// 异步工作
}
// 解决方案
Task<Void, Never> {
try await work()
}
后台工作模式
经典的"主线程-后台线程-主线程"模式在Swift Concurrency中的实现:
方案1:异步包装器存在时
final class DemoViewController: UIViewController {
func doWork() {
beforeWorkBegins() // 立即在主线程执行
Task {
let result = await asyncExpensiveWork(arguments)
// 隐式回到MainActor上下文
afterWorkIsDone(result)
}
}
}
方案2:异步上下文调用
如果调用者已是异步上下文,可直接使用async函数:
final class DemoViewController: UIViewController {
func doWork() async {
beforeWorkBegins()
let result = await asyncExpensiveWork(arguments)
afterWorkIsDone(result)
}
}
方案3:无异步包装器时
为同步函数创建异步包装器:
func asyncExpensiveWork(arguments: Arguments) async -> Result {
await withCheckedContinuation { continuation in
DispatchQueue.global().async {
let result = expensiveWork(arguments)
continuation.resume(returning: result)
}
}
}
顺序依赖的异步工作
需要保证异步任务执行顺序时,可使用AsyncStream作为队列:
typealias WorkItem = @Sendable () async throws -> Void
let (stream, continuation) = AsyncStream<WorkItem>.makeStream()
// 启动单个Task处理所有工作项,保证顺序执行
Task {
for await workItem in stream {
try? await workItem()
}
}
// 添加工作项到流中
continuation.yield({
try await work()
})
continuation.yield({
try await anotherWork()
})
高级应用:构建线程安全的异步缓存
结合上述多种技术,我们可以构建一个功能完善的异步缓存系统。以下是一个支持取消、错误处理和并发控制的通用异步缓存实现:
actor AsyncCache<Value> where Value: Sendable {
typealias ValueContinuation = CheckedContinuation<Value, Error>
typealias ValueResult = Result<Value, Error>
enum State {
case empty
case pending(Task<Void, Never>, [UUID: ValueContinuation])
case filled(ValueResult)
}
private var state = State.empty
private let valueProvider: () async throws -> Value
private let fillOnCancel: Bool
init(fillOnCancel: Bool = true, valueProvider: @escaping () async throws -> Value) {
self.valueProvider = valueProvider
self.fillOnCancel = fillOnCancel
}
// ... 完整实现参见项目代码 ...
public var value: Value {
get async throws {
switch state {
case let .filled(value):
return try value.get()
case .pending:
let id = UUID()
return try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
trackContinuation(continuation, with: id)
}
} onCancel: {
Task { await self.cancelContinuation(with: id) }
}
case .empty:
let task = Task {
do {
let value = try await makeValue()
fill(with: .success(value))
} catch {
fill(with: .failure(error))
}
}
self.state = .pending(task, [:])
return try await withTaskCancellationHandler(operation: {
try await withCheckedThrowingContinuation { continuation in
trackContinuation(continuation, with: UUID())
}
}, onCancel: {
Task { await self.cancelInitialTask() }
})
}
}
}
}
项目使用指南
快速开始
- 克隆项目仓库:
git clone https://gitcode.com/gh_mirrors/co/ConcurrencyRecipes.git
cd ConcurrencyRecipes
- 构建项目:
swift build
- 运行测试:
swift test
集成到你的项目
将ConcurrencyRecipes集成到你的Swift项目中,最简单的方式是通过Swift Package Manager:
dependencies: [
.package(url: "https://gitcode.com/gh_mirrors/co/ConcurrencyRecipes.git", from: "1.0.0")
]
总结与最佳实践
Swift Concurrency为异步编程带来了革命性的变化,但也带来了新的挑战。ConcurrencyRecipes项目通过提供经过验证的解决方案,帮助开发者克服这些挑战。以下是使用Swift Concurrency的核心最佳实践:
-
优先使用结构化并发:尽可能使用async/await而非手动创建Task,以确保任务树正确构建和取消传播。
-
正确处理Actor隔离:理解Sendable协议和隔离域概念,避免数据竞争。
-
谨慎管理异步上下文:注意同步到异步上下文的转换,避免意外的线程切换。
-
实现健壮的错误处理:不要忽略异步操作抛出的错误,设计清晰的错误传播路径。
-
考虑取消语义:在长时间运行的操作中支持任务取消,释放资源并避免无效工作。
随着Swift Concurrency的不断发展,ConcurrencyRecipes项目也将持续更新,提供更多针对新特性和最佳实践的解决方案。无论是新手还是有经验的Swift开发者,都能从这些实用模式中获益,构建更可靠、高效的并发代码。
延伸学习资源
- Swift官方文档:Swift Concurrency
- Swift by Sundell:Concurrency articles
- Point-Free:Concurrency series
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



