Swift Concurrency中Continuation的超时机制实现问题
优化Swift Concurrency代理封装的三个核心问题解决方案
针对代理转async/await时遇到的三个问题,以下是具体优化方案,结合原子状态控制、任务取消协作和弱引用机制从根源解决痛点:
核心设计思路
通过原子状态管理控制continuation生命周期,配合任务取消联动避免重复resume,同时用弱引用打破循环引用,确保逻辑安全且无内存泄漏。
1. 确保continuation仅被resume一次
给GenericContinuationWrapper添加原子状态标记,限制只有处于pending状态时才能执行resume操作,resume后立即切换为completed状态,彻底杜绝重复调用。
示例代码(iOS 16+可用Swift原生Atomic,低版本可替换为NSLock或os_unfair_lock):
import OSLog enum ContinuationState { case idle, pending, completed } class GenericContinuationWrapper<Success, Failure: Error> { private let state = Atomic(ContinuationState.idle) private var continuation: CheckedContinuation<Success, Failure>? func start() async throws -> Success { try await withCheckedThrowingContinuation { [weak self] continuation in guard let self = self else { return } // 仅idle状态允许启动新任务 guard self.state.compareExchange(expected: .idle, desired: .pending).exchanged else { continuation.resume(throwing: NSError(domain: "com.example", code: -1, userInfo: [NSLocalizedDescriptionKey: "任务已在执行中"])) return } self.continuation = continuation self.triggerProxyAction() } } // 代理回调时调用此方法 func resume(with result: Result<Success, Failure>) { guard state.compareExchange(expected: .pending, desired: .completed).exchanged else { os_log("Continuation已完成,忽略重复resume", log: .default, type: .debug) return } continuation?.resume(with: result) continuation = nil // 清空引用释放资源 } private func triggerProxyAction() { // 执行实际代理触发逻辑,如蓝牙指令发送 } }
2. 修复超时场景下的"Swift Task Continuation misused"错误
通过任务取消联动实现超时机制:启动超时Task的同时监听主任务取消信号;代理回调触发resume时立即取消超时Task;超时触发时先检查状态再执行resume,避免重复操作。
在start()方法中集成超时逻辑:
func start(timeout: TimeInterval = 10) async throws -> Success { try await withTaskCancellationHandler { try await withCheckedThrowingContinuation { [weak self] continuation in guard let self = self else { return } guard self.state.compareExchange(expected: .idle, desired: .pending).exchanged else { continuation.resume(throwing: NSError(domain: "com.example", code: -1, userInfo: [NSLocalizedDescriptionKey: "任务已在执行中"])) return } self.continuation = continuation self.triggerProxyAction() // 启动超时Task Task { [weak self] in try await Task.sleep(nanoseconds: UInt64(timeout * 1e9)) guard let self = self else { return } // 仅pending状态触发超时resume if self.state.compareExchange(expected: .pending, desired: .completed).exchanged { continuation.resume(throwing: NSError(domain: "com.example", code: -2, userInfo: [NSLocalizedDescriptionKey: "操作超时"])) self.continuation = nil } } } } onCancel: { [weak self] in // 主任务取消时标记状态并清空continuation guard let self = self else { return } if self.state.compareExchange(expected: .pending, desired: .completed).exchanged { self.continuation?.resume(throwing: CancellationError()) self.continuation = nil } } }
3. 避免超时场景下的内存泄漏
- 对
GenericContinuationWrapper内部的代理对象使用弱引用,避免强引用循环; - 任务完成(resume/超时/取消)后立即清空
continuation引用,释放相关资源; - 超时Task中捕获
self时使用弱引用,防止Task持有wrapper导致内存泄漏。
补充代理回调的弱引用示例:
class BluetoothManager: NSObject, BluetoothDelegate { private let wrapper = GenericContinuationWrapper<Data, Error>() func performAction() async throws -> Data { return try await wrapper.start(timeout: 10) } // 代理方法 func bluetoothDidReceiveData(_ data: Data) { wrapper.resume(with: .success(data)) } func bluetoothDidFail(with error: Error) { wrapper.resume(with: .failure(error)) } override init() { super.init() // 蓝牙SDK代理设置支持弱引用,若不支持可通过中间层转弱引用 bluetoothSDK.delegate = self } }
关键注意事项
- 兼容iOS 15及以下版本时,可将
Atomic替换为NSLock包裹状态变量,确保线程安全; - 蓝牙场景下超时时间建议设置为10-30秒,可根据实际需求调整;
- UI层可通过判断wrapper的状态,在重复点击时直接禁用按钮或提示用户任务正在执行,减少进入错误分支的概率。
内容的提问来源于stack exchange,提问作者svranaw
相关产品推荐
相关产品推荐

