取消DispatchWorkItem无法中断含DispatchSemaphore的循环如何解决
问题原因
你的取消逻辑仅在每轮循环的开头检测isCancelled状态,当代码执行到semaphore.wait()时,线程会进入阻塞状态。此时即使调用了cancel(),代码也无法走到下一轮的状态判断逻辑,只能等待当前addSticker的回调触发signal()后才会继续执行,这就是取消不生效的核心原因。
解决方案1:现有代码最小改动(带超时轮询检测取消状态)
把无限等待的semaphore.wait()替换为带超时的轮询等待,每隔一小段时间唤醒一次检查任务是否已被取消,无需等待信号量触发即可响应取消操作:
func performStickerWorkItems(_ stickers: [String]) { let queue = DispatchQueue.global(qos: .background) workItem = DispatchWorkItem { [weak self] in for sticker in stickers { guard let item = self?.workItem, !item.isCancelled else { print("cancelled") break } let semaphore = DispatchSemaphore(value: 0) self?.addSticker(sticker: sticker) { print("S: \(sticker)") semaphore.signal() } // 替换原来的 semaphore.wait() while true { // 每100毫秒唤醒一次检查取消状态,可根据业务场景调整间隔 let waitResult = semaphore.wait(timeout: .now() + 0.1) if waitResult == .success || self?.workItem?.isCancelled == true { break } } // 检测到取消直接终止整个循环 if self?.workItem?.isCancelled == true { break } } self?.workItem = nil } queue.async(execute: workItem!) workItem?.notify(queue: .main) { [weak self] in self?.updateButton(false) } }
改动后最多会有你设置的超时间隔的延迟,就可以响应取消操作,终止后续任务。
解决方案2:更优雅的原生实现(用OperationQueue管理任务)
如果不想自己维护信号量和任务状态,可以使用自带取消能力的OperationQueue管理串行任务,实现更简洁易维护:
// 声明私有属性 private let stickerTaskQueue: OperationQueue = { let queue = OperationQueue() queue.maxConcurrentOperationCount = 1 // 串行执行,和原semaphore的顺序执行逻辑一致 queue.qualityOfService = .background return queue }() func performStickerWorkItems(_ stickers: [String]) { // 先清空旧的未执行任务 stickerTaskQueue.cancelAllOperations() for sticker in stickers { let task = BlockOperation { [weak self] in guard let self = self, !task.isCancelled else { return } let semaphore = DispatchSemaphore(value: 0) self.addSticker(sticker: sticker) { guard !task.isCancelled else { return } print("S: \(sticker)") semaphore.signal() } // 可加最大超时防止单个任务异常导致永久阻塞 _ = semaphore.wait(timeout: .now() + 30) } stickerTaskQueue.addOperation(task) } // 所有任务完成后的回调 stickerTaskQueue.addBarrierBlock { [weak self] in DispatchQueue.main.async { self?.updateButton(false) } } } @IBAction func backWasPressed(_ sender: Any) { stickerTaskQueue.cancelAllOperations() // 直接取消所有未执行、执行中的任务 navigationController?.popViewController(animated: true) }
内容的提问来源于stack exchange,提问作者Bhavin Bhadani
相关产品推荐
相关产品推荐

