Swift Actor与Continuation结合的线程安全性问题咨询
问题分析与代码修正
你的代码在activeTask管理、Continuation安全使用以及Actor可重入性适配这几个点上存在问题,下面逐一说明并给出修正后的实现:
核心问题点
- activeTask 生命周期管理错误:启动扫描后立刻将
activeTask置为nil,会导致后续调用无法识别当前正在进行的扫描任务,进而重复创建新任务,违背了跟踪活跃任务的初衷。 - Continuation 安全与线程适配:
CBCentralManager的回调并非在Actor的串行队列执行,直接在回调中resume continuation可能引发线程安全问题;同时未处理蓝牙状态错误(如蓝牙未开启)的场景,会导致continuation永久挂起。 - 类型不匹配:
withCheckedThrowingContinuation的泛型类型是BLEDevice,但外层Task和返回值是BLEDevice?,存在类型不一致问题。
修正后的代码实现
public actor MyNewDelegateSub { private var activeTask: Task<BLEDevice, Error>? private let bleDelegate: BluetoothLEDelegate public init() { let delegate = BluetoothLEDelegate() self.bleDelegate = delegate // 将Actor的回调处理逻辑传给delegate,确保在Actor上下文处理resume delegate.onDeviceDiscovered = { [weak self] device in Task { [weak self] in await self?.handleDeviceDiscovery(device: device) } } delegate.onScanError = { [weak self] error in Task { [weak self] in await self?.handleScanError(error: error) } } } public func scanForDevice() async throws -> BLEDevice { // 若已有活跃任务,直接等待其结果 if let existingTask = activeTask { return try await existingTask.value } let task = Task { [weak self] in guard let self = self else { throw CancellationError() } return try await withCheckedThrowingContinuation { continuation in // 将continuation暂存到Actor内部,而非直接给delegate self.bleDelegate.setContinuation(continuation) do { try self.bleDelegate.startScan() } catch { continuation.resume(throwing: error) } } } activeTask = task do { let device = try await task.value activeTask = nil // 任务完成后清除活跃任务标记 return device } catch { activeTask = nil // 任务失败或取消后也需清除标记 throw error } } private func handleDeviceDiscovery(device: BLEDevice) { bleDelegate.resumeContinuation(with: .success(device)) activeTask = nil } private func handleScanError(error: Error) { bleDelegate.resumeContinuation(with: .failure(error)) activeTask = nil } } private final class BluetoothLEDelegate: NSObject, CBCentralManagerDelegate { private let centralManager = CBCentralManager() private var continuation: CheckedContinuation<BLEDevice, Error>? var onDeviceDiscovered: ((BLEDevice) -> Void)? var onScanError: ((Error) -> Void)? override init() { super.init() centralManager.delegate = self } func setContinuation(_ continuation: CheckedContinuation<BLEDevice, Error>?) { self.continuation = continuation } func startScan() throws { guard centralManager.state == .poweredOn else { throw BluetoothError.unavailable(centralManager.state) } centralManager.scanForPeripherals(withServices: nil) } func resumeContinuation(with result: Result<BLEDevice, Error>) { continuation?.resume(with: result) continuation = nil centralManager.stopScan() } // MARK: - CBCentralManagerDelegate func centralManagerDidUpdateState(_ central: CBCentralManager) { // 蓝牙状态变更为不可用时,触发错误回调 if central.state != .poweredOn, continuation != nil { onScanError?(BluetoothError.unavailable(central.state)) } } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { onDeviceDiscovered?(BLEDevice(peripheral)) } } // 自定义蓝牙错误类型 enum BluetoothError: Error { case unavailable(CBCentralManager.State) } // 假设的BLEDevice模型 struct BLEDevice { let peripheral: CBPeripheral init(_ peripheral: CBPeripheral) { self.peripheral = peripheral } }
关键修正说明
- activeTask 正确管理:仅在任务成功、失败或取消后才清除
activeTask,确保并发调用能正确识别并等待已有任务结果,适配Actor的可重入特性。 - Continuation 线程安全:通过闭包将蓝牙回调转发到Actor上下文处理,避免在非Actor队列直接操作continuation;同时统一在Actor内管理continuation的resume逻辑,确保线程安全。
- 错误场景覆盖:新增蓝牙状态错误处理,避免因蓝牙未开启导致continuation永久挂起;任务取消时也会自动清理continuation和activeTask。
- 类型一致性:统一返回值类型为非可选的
BLEDevice,若扫描无结果可通过自定义错误抛出,语义更清晰。
内容的提问来源于stack exchange,提问作者isenwald2022
相关产品推荐
相关产品推荐

