OperationQueue 的调度机制可以看作是一个高度智能的任务管家。虽然它的底层完全构建在 GCD 之上,但它引入了一套复杂的“准入与执行”逻辑,通过状态机管理来决定哪一个 Operation 应该被投入到真正的执行队列中。
以下是其内部调度的核心流程:
1. 任务的“准入检查”阶段
当你调用 addOperation: 时,任务并不会立即执行。OperationQueue 会持续监控每个任务的 isReady 属性。
一个 Operation 要进入“准备就绪”状态,必须满足以下两个条件:
- 依赖关系解除:通过
addDependency:添加的所有前置任务必须都已经完成(isFinished = YES)。 - 业务逻辑就绪:如果是自定义子类,你重写的
isReady属性必须返回YES。
2. 调度决策:优先级与并发数
一旦有多个任务同时处于 isReady 状态,OperationQueue 会根据以下规则进行分发:
-
队列优先级 (
queuePriority) :它不是严格的执行顺序,而是“权重”。在高并发环境下,优先级高的任务更有可能被调度器选中并分配到线程。
-
最大并发数控制 (
maxConcurrentOperationCount) :这是调度的“阀门”。
- 如果该值为
1,队列表现为串行。 - 如果该值为
默认值 (-1),系统根据当前 CPU 负载和内存情况自动决定。 - 如果当前正在执行的任务数已达到上限,即使其他任务已
isReady,它们也必须在缓冲区等待。
- 如果该值为
3. 执行阶段:底层 GCD 的介入
一旦调度器决定执行某个任务,真正的魔法就开始了:
- 封装与分发:
OperationQueue内部维护着一个私有 GCD 队列(通常是一个特殊的DISPATCH_QUEUE_CONCURRENT)。 - 生命周期管理:队列会调用
Operation的start方法。此时,任务进入isExecuting状态。 - 线程池利用:由于是基于 GCD 的,
OperationQueue会根据任务的qualityOfService(QoS) 智能地向系统申请或复用线程。
4. 关键:为什么 cancel 不代表“立即停止”?
理解调度机制最重要的一点是:OperationQueue 只能管理任务的“启动”,不能暴力切断正在运行的“过程”。
- 调度器的行为:当你调用
cancel时,队列会检查尚未开始的任务。如果任务还在等待队列里,它会被标记为isCancelled和isFinished,然后直接跳过执行。 - 正在执行的任务:对于已经在线程中运行的任务,
OperationQueue仅仅是把任务的isCancelled属性设为YES。 - 开发者职责:你必须在自己的任务逻辑中定期检查
isCancelled状态。
Swift
// 正确的调度响应姿势
override func main() {
for i in 0..<100 {
// 每步操作前检查调度器是否已经发出了取消信号
if isCancelled { return }
// 执行具体耗时操作...
}
}
5. 调度机制总结
| 调度环节 | 核心机制 | 作用 |
|---|---|---|
| 入队 | 依赖检查 | 确保任务按逻辑顺序就绪。 |
| 排序 | queuePriority | 决定 Ready 状态任务的抢占顺序。 |
| 限流 | maxConcurrentOperationCount | 防止过度消耗系统资源。 |
| 分发 | 私有 GCD 并发队列 | 利用内核能力进行线程切换和负载均衡。 |
| 完成 | KVO 监听 isFinished | 触发后续依赖任务的 Ready 状态。 |
💡 深度洞察
OperationQueue 实际上是实现了一个生产者-消费者模型。你作为生产者不停地往里塞任务,它作为消费者根据系统的“配额”(并发数)和“合同”(依赖关系)来消耗任务。