You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 05:43:10