使用DispatchQueue.global()作为Combine delay调度器致发布值丢失
1. 调度器优先级与系统调度策略差异
DispatchQueue.global()默认使用default QoS(服务质量),而DispatchQueue.main采用的是userInteractive QoS——这是系统优先级最高的等级。当系统负载较高时,系统会优先调度高QoS队列的任务,低QoS的global队列任务可能被延迟,甚至在你设置的1秒等待时间内无法完成调度执行。这就导致timeoutPublisher的延迟任务偶尔无法及时发布0值,ok变量自然不会被设置为true。
而main队列的任务优先级高,系统会确保其任务被及时调度,因此delay操作能稳定触发,不会出现“卡住”的情况。
2. 并发队列的任务调度不确定性
DispatchQueue.global()是并发队列,其线程池的线程分配是动态且不确定的。当短时间内创建大量延迟任务(你的循环执行100次),可能出现线程池资源临时耗尽的情况,后续的延迟任务需要等待空闲线程才能执行。如果等待时间超过你设置的1秒Task.wait时长,就会出现0值未发布的情况。
而main队列是串行队列,任务按顺序执行,不会出现线程资源竞争的问题,因此执行结果稳定。
3. Swift Concurrency与GCD的调度交互影响
你使用了Task.wait(.seconds(1))这个Swift Concurrency API暂停当前任务,它和GCD的调度系统相互独立。当系统资源紧张时,Swift Concurrency的线程池可能占用大量线程资源,导致GCD的global队列无法获取足够的线程来执行延迟任务,进而出现任务未及时执行的情况。
如果必须使用后台队列作为调度器,可以通过以下方式调整:
- 自定义指定高QoS的并发队列,提升任务优先级:
let scheduler = DispatchQueue.global(qos: .userInitiated) - 避免短时间内创建大量独立的Combine订阅,尽量复用订阅或合并任务,减少线程资源消耗。
- 如果不需要并发执行,改用串行后台队列替代全局并发队列,避免线程竞争。
内容的提问来源于stack exchange,提问作者Michał Śmiałko

