如何正确处理 CoreBluetooth 超时与 Task Cancellation
å¦ä½æ£ç¡®å¤ç CoreBluetooth è¶ æ¶ä¸ Task Cancellation
ilawsonlu 2026-08-20 3 é 读6åéå¦ä½æ£ç¡®å¤ç CoreBluetooth è¶ æ¶ä¸ Task Cancellation
CoreBluetooth çå 乿æç»æé½é åè°è¿åï¼èåè°å¯è½æ°¸è¿ä¸æ¥ã没æè¶ æ¶ï¼ä½ ç
await read()ä¼ä¸ç´ææ»ï¼å¤çä¸å¥½åæ¶ï¼ä»»å¡éåºäºèçè¿å¨åå°æ«ãæ¬æè®²æ¸ æ¥"åè°è½¬ async"éæå®¹æè¢«å¿½ç¥ç两件äºï¼è¶ æ¶ååæ¶ï¼ä»¥åå®ä»¬ä¹é´é£åºä½ èº²ä¸æçç«æã
ä¸ã为ä»ä¹ CoreBluetooth ç¹å«éè¦è¶ æ¶
çä¸ç»åè°ï¼
func centralManager(_:didConnect:) // è¿æ¥æå
func peripheral(_:didDiscoverServices:) // æå¡åç°å®æ
func peripheral(_:didDiscoverCharacteristicsFor:) // ç¹å¾åç°å®æ
func peripheral(_:didUpdateValueFor:) // 读å°å¼äº
func peripheral(_:didWriteValueFor:) // å宿
å®ä»¬çå ±åç¹æ¯ï¼æ²¡æä¸ä¸ªæ¯ä¿è¯ä¼æ¥çã设å¤ä¿¡å·å·®ãåºä»¶å¡æ»ãçè³åªæ¯æä¸ªå¤è®¾ä¸æè§èåå¤ï¼å¯¹åºåè°å°±å¯è½æ°¸è¿ä¸è§¦åã
å¦æä½ æè¿æ ·çåè°ç´æ¥è½¬æ asyncï¼å°±ä¼å¾å°ä¸ä¸ª"æ°¸è¿æèµ·"ç awaitââUI å¡ä½ãå
åéç continuation æ¬çãä¸å±é»è¾åæãæä»¥è¶
æ¶ä¸æ¯é¦ä¸æ·»è±ï¼èæ¯æ¯ä¸ªæä½é½å¿
é¡»æçå
åºã
äºãåè°è½¬ async çèµ·ç¹ï¼withCheckedThrowingContinuation
æåè°è½¬æå¯çå¾ çæä½ï¼æ ååæ³æ¯ï¼
let value = try await withCheckedThrowingContinuation { continuation in
peripheral.readValue(for: characteristic)
// ç didUpdateValueFor åè°é continuation.resume(...)
}
CheckedContinuation ä¼å¨ç¼è¯æ + è¿è¡æå¸®ä½ æ£æ¥"æ¯å¦æ¼äº resume"æ"éå¤ resume"ï¼æ¯ä¸ªå¾å¥½çå®å
¨ç½ã
ä½è¿éç«å»ååºç¬¬ä¸ä¸ªåï¼å¦æ didUpdateValueFor æ°¸è¿ä¸æ¥ï¼è°æ¥ resumeï¼ çæ¡æ¯ä½ èªå·±å¾å®æä¸ä¸ªè¶
æ¶ä»»å¡æ¥å
åºã第äºä¸ªåæ¯ï¼å¦æå¤å± Task è¢«åæ¶äºï¼è°æ¥ååºï¼ çæ¡æ¯ä½ å¾åå¥ä¸å± withTaskCancellationHandlerã
äºæ¯éª¨æ¶åæäºï¼
func read(timeout: TimeInterval) async throws -> Data {
try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
// â åèµ·æä½
// ⡠注åè¶
æ¶ä»»å¡
// ⢠注ååæ¶å¤ç
}
} onCancel: {
// åæ¶æ¶çæ£åæ¢åºå±æä½
}
}
ä¸ãä¸ä»¶äºæ¢ç resumeï¼ç«ææ¯æ ¸å¿é¾ç¹
ä¸ä¸ªæä½ä»åèµ·å°ç»æï¼å¯è½æä¸ä¸ªæ¥æºé½æ³ resume åä¸ä¸ª continuationï¼
- ç»æå°äº ââ
didUpdateValueForåè° âresume(returning:)ï¼ - è¶
æ¶äº ââ ä½ èµ·ç
Task.sleep(timeout)å°æ âresume(throwing: .timedOut)ï¼ - è¢«åæ¶äº ââ å¤å± Task åæ¶ â
resume(throwing: .cancelled)ã
è CheckedContinuation æä¸ªéå¾ï¼åªè½ resume 䏿¬¡ãéå¤ resume ä¼ç´æ¥å´©æºï¼è¿æ£æ¯å®"Checked"è¦æçé®é¢ï¼ã
æ³è±¡ä¸ä¸ªçå®åºæ¯ï¼è¶
æ¶æ¶å»åç»æåè°å ä¹åæ¶å°è¾¾ââè¶
æ¶ä»»å¡å resume(throwing: .timedOut)ï¼åè°å resume(returning: value)ï¼boomã
è§£æ³æ¯ç¨ä¸æé + ä¸ä¸ª completed æ å¿åäºæ¥ï¼è°å
å°è°çæï¼å
¶ä½éé»å¿½ç¥ï¼
private var completed = false
private let lock = NSLock()
func finish(_ body: () -> Void) {
lock.lock()
guard !completed else { lock.unlock(); return } // å·²ç»æäºº resume è¿äº
completed = true
lock.unlock()
body() // è¿éæçæ£ resume
}
ä¸ä¸ªæ¥æºé½éè¿åä¸ä¸ª finish æ¶å£ï¼å°±è½ä¿è¯æ 论å®ä»¬ä»¥ä»ä¹é¡ºåºãä»ä¹å¹¶å度å°è¾¾ï¼é½åªæä¸æ¬¡ resume çæ£æ§è¡ã
åãåæ¶ä¸åªæ¯"ä¸ resume"ï¼è¦çæ£åæåºå±æä½
å¾å¤äººå¨ withTaskCancellationHandler ç onCancel éåªåä¸ä»¶äºââcontinuation.resume(throwing: CancellationError())ãè¿è½è®© await æéè¿åï¼ä½åºå±æä½è¿å¨è·ã
对 CoreBluetooth æ¥è¯´ï¼"åæ¶"å¿ é¡»è½å°å®å¤ï¼
| æä½ | åæ¶æ¶çæ£è¯¥åä»ä¹ |
|---|---|
| æ«æ | central.stopScan() |
| è¿æ¥ | central.cancelPeripheralConnection(peripheral) |
| 读å/åç° | ç»ææèµ·ç continuationï¼åºå±æ ç¬ç«åæ¶ APIï¼é è¶ æ¶/æå¼å åºï¼ |
ä¹å°±æ¯è¯´ï¼åæ¶çæ£ç¡®å§¿å¿æ¯ï¼æ¢è®©ä¸å± await æéï¼å让åºå±èµæºè¢«éæ¾ã两è
缺ä¸ï¼è¦ä¹çé¢éåºäºèçè¿å¨èçµæ«æï¼è¦ä¹èµæºæ³æ¼ã
å¦å¤æ³¨æä¸ä¸ªç»èï¼ä»»å¡åæ¶æåºæ¥çæ¯ CancellationErrorï¼ä½ä¸å¡ä¸å¾å¾æ´å¸æä¸ä¸ªç»ä¸ãå¯è¯»çé误ãæåæ¶å½ä¸åæèªå®ä¹éè¯¯ï¼æ¯å¦ .operationCancelledï¼ï¼ä¸å± catch æ¶é»è¾æ´æ¸
ç½ï¼
do { try await client.findDevice(...) }
catch BLEError.operationCancelled { /* ç¨æ·åæ¶ */ }
catch BLEError.scanTimedOut { /* è¶
æ¶ */ }
äºãçå¾ "就绪"乿¯ä¸ç§è¶ æ¶åºæ¯
é¤äºå个 GATT æä½ï¼è¿æä¸ä¸ªå®¹æè¢«å¿½ç¥çè¶
æ¶ï¼çèçæ¬èº«å°±ç»ªãCBCentralManager å¯å¨åï¼ç¶æä¼ç»å .unknown â .poweredOn çè·³åï¼æé´ä¸è½åèµ·æ«æãæä»¥"ç就绪"ä¹è¦å¸¦è¶
æ¶ã
ä¸ä¸ªä¼é
çåæ³æ¯ç«éï¼raceï¼ï¼è®©"ç¶æå°è¾¾ .poweredOn"å"è¶
æ¶"两个任å¡èµè·ï¼è°å
宿è°èµ¢ï¼
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { /* çå¬ bluetoothStatesï¼çå° .poweredOn å°± return */ }
group.addTask { try await Task.sleep(timeout); throw .readyTimedOut }
defer { group.cancelAll() } // 䏿¹èåºååæ¶å¦ä¸æ¹
_ = try await group.next()
}
TaskGroup 天ç¶éåè¿ç§"N éä¸"çç«éï¼å
宿çç»æè¿åï¼defer { cancelAll() } è´è´£æ¸
çè¾æçé£ä¸æ¹ã
å ãArcBLEKit æä¹è½å°è¿ä¸æ´å¥
ä¸é¢è¿äºæ¨¡å¼ï¼ArcBLEKit å ¨é¨å 建äºï¼ä½ å 乿ç¥ä¸å°å®ä»¬çåå¨ï¼
let value = try await session.read(
characteristic: CBUUID(string: "FFF1"),
service: CBUUID(string: "FFF0"),
options: GATTOperationOptions(timeout: 10) // æ¯ä¸ªæä½èªå¸¦è¶
æ¶
)
ä¸ä¸ª GATTOperationOptions(timeout:)ï¼æè¶
æ¶ç»å°äºè¯»ãåãåç°ã订é
çæææä½ä¸ï¼èä»»å¡åæ¶å贯穿å§ç»ââåæ¶æ«æä¼ stopScanï¼åæ¶è¿æ¥ä¼ cancelPeripheralConnectionã
è¶ æ¶ååæ¶åçæ¶ï¼é误æ¯å ·ä½ä¸å¯åºåçï¼
public enum BLEError {
case connectionTimedOut(UUID)
case scanTimedOut
case gattOperationTimedOut(GATTOperation, service: CBUUID?, characteristic: CBUUID?)
case operationCancelled
// ...
}
gattOperationTimedOut çè³å¸¦ä¸äºæ¯åªä¸ªæä½è¶
æ¶ï¼serviceDiscovery / read / write / notificationSetupâ¦ï¼ï¼ææ¥çº¿ä¸é®é¢æ¶ä¸è³äºå¯¹çä¸ä¸ªç¬¼ç»ç"è¶
æ¶"åæµã
è¿æ¥ãæ«æã就绪çå¾ åæ ·æè¶ æ¶ï¼
try await client.waitUntilReady(timeout: 10) // çèç就绪
let device = try await client.findDevice(matching: filter, timeout: 10) // æ¾è®¾å¤
try await client.connect(to: device, options: .init(timeout: 10)) // è¿æ¥
å®ç°å±é¢ï¼ArcBLEKit ç»ä¸ç¨ withTaskCancellationHandler + withCheckedThrowingContinuation + è¶
æ¶ Task çç»åï¼å¹¶ç¨ä¸é¢é£å¥"é + æ å¿ä½"ä¿è¯åª resume 䏿¬¡ï¼æææèµ·çæä½å¨æå¼æ¶ä¼éè¿ failAll 䏿¬¡æ§ç»æï¼ç»ä¸çæ¬çç continuationã
ä¸ä¸ªå®ç°ç»èï¼CoreBluetooth çç±»å大å¤ä¸æ¯
Sendableï¼è¦å¨ Swift Concurrency éå®å ¨å°ææå®ä»¬ï¼éè¦@preconcurrency import CoreBluetoothå ä¸å±@unchecked Sendableçå è£ ãè¿ä¹æ¯ä¸ºä»ä¹"æåå°è£ "å¾å¾æ¯æ³è±¡ä¸æ´è´¹å²ââå¹¶åå®å ¨çåä¸ä¸ªæ¥ä¸ä¸ªã
ä¸ãå°ç»
"åè°è½¬ async"è¿ä»¶äºï¼çæ£é¾ç䏿¯ withCheckedThrowingContinuation æ¬èº«ï¼èæ¯å®èåå¿
须补é½ç两åï¼
- è¶ æ¶ ââ æ¯ä¸ªæä½é½è¦æå åºï¼å¦åææ»ï¼
- åæ¶ ââ æ¢è¦è®©
awaitæéï¼åè¦çæ£éæ¾åºå±èµæºã
以忍ªè·¨ä¸¤è ä¹ä¸çé£ä¸ªç«æï¼ç»æãè¶ æ¶ãåæ¶ä¸ä¸ªæ¥æºæ¢ç resumeï¼å¿ é¡»ç¨äºæ¥ä¿è¯åª resume 䏿¬¡ã
ArcBLEKit æè¿å¥æºå¶æ²æ·æäºé»è®¤è¡ä¸ºââè¶ æ¶å 建ãåæ¶è´¯ç©¿ãéè¯¯å ·ä½ãæå¼æ¶ç»ä¸æ¸ çãä¸ä¸ç¯æä»¬ç»§ç»å¾ä¸èµ°ï¼è®²ä¸ä¸ªä¾èµè¿å¥æºå¶çè¿é¶è¯é¢ï¼éè¿ä¹åå¦ä½æ¢å¤ Notificationã
æ¬ç³»åï¼
- ç¨ AsyncThrowingStream å°è£ CoreBluetooth æ«æ
- iOS BLE èªå¨éè¿ä¸ºä»ä¹æ¯æ³è±¡ä¸å¤æ
- å¦ä½æ£ç¡®å¤ç CoreBluetooth è¶ æ¶ä¸ Task Cancellationï¼æ¬æï¼
- BLE éè¿åå¦ä½æ¢å¤ Notification
- writeWithoutResponse çèåå¤ç
ð¦ ArcBLEKit on GitHub · ð API ææ¡£
Aitishiku.com