串行DispatchQueue中asyncAfter执行块未被调用的问题
asyncAfter未执行的问题 看起来你遇到了串行队列嵌套任务导致的阻塞问题,同时代码里还有个状态维护的小疏漏,我来帮你拆解并解决:
核心问题分析
你的代码存在两个关键问题,共同导致了asyncAfter任务无法执行,还破坏了“防止函数被多端同时调用”的逻辑:
1. 同串行队列的同步闭包内提交异步任务会阻塞
当你在serialQueue.sync的闭包中,向同一个串行队列提交asyncAfter任务时,这个异步任务会被放到队列的等待队列里。但串行队列一次只能处理一个任务,当前正在执行的是sync的闭包,必须等这个闭包执行完毕,队列才会处理下一个任务。如果你的sync闭包没结束(比如在等asyncAfter的结果),就会直接死锁;就算闭包结束了,asyncAfter的执行时机也完全不符合预期。
2. 未重置执行状态变量
你在代码里把isProcessGoingOn设为true后,没有在任务(包括模拟的网络请求、asyncAfter任务)完成后重置为false。这会导致第一次调用funcA后,后续所有调用都会直接返回,永远无法再次执行任务。
解决方案
根据你的需求(防止函数被多端同时调用),我提供两种常用的修复方案:
方案1:用串行队列天然的串行性管理任务(推荐)
不需要手动维护isProcessGoingOn,串行队列本身就能保证同一时间只有一个任务在执行。根据你是否需要新调用直接返回,又分两种情况:
情况A:允许任务排队执行
如果希望所有funcA调用按顺序执行,不需要直接返回,代码可以简化成这样:
let serialQueue = DispatchQueue(label: "co.random.queue") func funcA() { serialQueue.async { debugPrint("Executing Code") // 模拟网络请求或异步任务,这里用全局队列执行asyncAfter DispatchQueue.global().asyncAfter(deadline: .now() + 2) { debugPrint("Async task completed") // 如果需要更新状态,回到串行队列操作(保证线程安全) serialQueue.async { // 这里处理任务完成后的状态更新 } } } }
情况B:已有任务执行时,新调用直接返回
如果需要在任务正在执行时,新调用直接返回,我们可以在串行队列内维护状态:
let serialQueue = DispatchQueue(label: "co.random.queue") private var isProcessing = false // 私有变量,仅在串行队列内读写 func funcA() { serialQueue.async { guard !self.isProcessing else { debugPrint("Returned") return } self.isProcessing = true debugPrint("Executing Code") // 模拟网络请求和asyncAfter任务 DispatchQueue.global().asyncAfter(deadline: .now() + 2) { debugPrint("Async task completed") // 回到串行队列重置状态,保证线程安全 self.serialQueue.async { self.isProcessing = false } } } }
方案2:必须使用同步调用时的处理
如果你需要funcA的调用者等待任务完成(即保留sync的行为),绝对不能在sync闭包内向同一个串行队列提交异步任务。可以把异步任务放到全局队列执行,并用信号量等待完成:
let serialQueue = DispatchQueue(label: "co.random.queue") private var isProcessGoingOn = false func funcA() { serialQueue.sync { guard !self.isProcessGoingOn else { debugPrint("Returned") return } self.isProcessGoingOn = true debugPrint("Executing Code") let semaphore = DispatchSemaphore(value: 0) // 使用全局队列执行asyncAfter,避免阻塞串行队列 DispatchQueue.global().asyncAfter(deadline: .now() + 2) { debugPrint("Async task completed") semaphore.signal() // 回到串行队列重置状态 self.serialQueue.async { self.isProcessGoingOn = false } } // 等待异步任务完成(仅当需要调用者等待时使用) semaphore.wait() } }
关键注意事项
- 永远不要在串行队列的同步闭包中,向同一个串行队列提交异步任务,这是死锁的高发场景。
- 所有状态变量(比如
isProcessing)的读写必须在同一个串行队列内进行,避免线程安全问题。 - 非必要情况下,优先使用
async而非sync,减少死锁风险,提升代码的响应性。
内容的提问来源于stack exchange,提问作者schinj

