NSOperation 的 qualityOfService 是否可以低于 NSOperationQueue?

Can NSOperation have a lower qualityOfService than NSOperationQueue?

NSOperationQueue.qualityOfService 的文档说:

This property specifies the service level applied to operation objects added to the queue. If the operation object has an explicit service level set, that value is used instead.

它没有提及操作和队列之间的相对质量,因此任何组合都应该有效。

但是,GCD 有一些相应的功能,其文档说:

DISPATCH_BLOCK_ENFORCE_QOS_CLASS

Flag indicating that execution of a dispatch block object submitted to a queue should prefer the QOS class assigned to the block (resp. associated with the block at the time of submission) over the QOS class assigned to the queue, as long as doing so will not result in a lower QOS class.

如果 NSOperationNSOperationQueue 建立在 GCD 之上——我相信这是真的——似乎限制也应该适用于它们。

因此,如果我将具有较低 qualityOfServiceNSOperation 添加到 NSOperationQueue 中,它会 运行 具有其较低的 qualityOfService 或队列的较高qualityOfService?

spindump 的timeline 模式将显示采样进程线程的 QoS。您可能需要做一些设计才能让 spindump 告诉您发生了什么,并让您了解 QoS 提升的工作原理。 QoS 提升的想法是防止优先级反转,因此用户启动的操作不会被饿死。

NSOperationQueue 的 QoS 实现不是构建在 GCD QoS 块原语之上。

NSOperationQueue 行为在 Foundation release notes 中有详细记录。

对于 GCD queues,执行具有分配或传播 QoS 的块时 QoS 降低的唯一时间是 queue 块被提交到 未指定 QoS(queue 是在没有 QoS 属性的情况下创建的,并且不针对 non-default 全局 queue)。在这种情况下,queue 最初由工作线程以 QoS class 默认值提供服务,并且允许降低。

有关详细信息,请参阅 dispatch/block.h header 或 2015 and 2014 的 GCD WWDC 会议中的文档。

感谢@das 将我指向 Foundation Release Notes for OS X v10.10

忽略操作依赖部分,NSOperationNSOperationQueue之间的QoS规则与dispatch block和dispatch queue之间的QoS规则完全相同——尽管@das指出"the QoS implementation of NSOperationQueue is not built on top of the GCD QoS block primitives"规则其实是一样的,就是说NSOperation的QoS(如果低)会提升到NSOperationQueue的QoS。所以我最初的猜测是正确的。 NSOperationQueue.qualityOfService 的文档是错误的:

If the operation object has an explicit service level set, that value is used instead.

下面是NSOperationNSOperationQueueQoS规则的详细解释:

NSOperationQueue qualityOfService

NSOperationQueue has a new qualityOfService property.

You can change the qualityOfService property at any time.

When an operation is added to a queue, the queue's qualityOfService value at that time may affect the effective QOS that the operation will be run at:

  • If the queue property has not been set, the operation is unaffected.
  • If the queue property has been set to NSQualityOfServiceDefault, the operation is unaffected.
  • If the queue property is set to another value, the operation is promoted to the queue's qualityOfService if its promotion QOS is not already at least that level.

If the qualityOfService property of a queue is changed while there are operations in the queue, the effective QOS of operations in the queue will be affected, just as the operations were when added to the queue. Thus, when the qualityOfService property of a queue is changed, all operations in the queue, running or not, have their effective QOS raised up to that level (if they were lower), and future additions to the queue are raised to that level (when they are lower). If the qualityOfService property is lowered from one level to another, only future additions will be affected by that new lower value. Operations' promotion or effective QOS is never lowered by the setting of, or the set value of, a queue's qualityOfService.

When an operation is added to a queue, the operation's effective QOS values at that time may affect the effective QOS of the operations which are already in the queue ("ahead of it"):

  • The operations already ahead in the queue of the newly added operation, running or not, are promoted to the effective QOS of the operation being added.

Thus, if a high QOS operation is added to a queue, operations already in the queue are raised up to that level (if they were lower). Operations added after that high-QOS operation are not affected by its presence in the queue.

The meaning and interaction of operation promotion QOS and effective QOS is discussed in the section on NSOperation qualityOfService.

NSOperationQueues do not infer any QOS from any execution context.

If the (dispatch_queue_t) underlyingQueue property of an NSOperationQueue is set, qualityOfService property values of NSOperationQueues and NSOperations have no effect. The effective QOS of operations run by that queue is determined by the state of the dispatch_queue_t.

NSOperation qualityOfService

NSOperation has a new qualityOfService property.

You can change the qualityOfService property at any time.

There are various real and virtual QOS values that relate to how an operation runs:

  • The qualityOfService property value
  • An inferred QOS
  • The promotion QOSes
  • The effective QOS

When an operation object is created, an inferred QOS value is computed from the execution context:

  • If either:
    • the operation is being created in the execution context of another operation (already running on that thread); or
    • the operation is being created in the execution context of a certain NSProcessInfo API; then the nearest one of those to the current activation frame of the call stack of the current thread is used as the inferred QOS of the new operation:
    • that operation's effective QOS at the time it started running;
    • the NSProcessInfo API's values are mapped to a QOS value.
  • If the operation is being created on the main thread, the inferred QOS is NSQualityOfServiceUserInitiated.
  • Otherwise, the current thread's QOS (which may be none) is read and used as the inferred QOS of the new operation.

An operation can be promoted (have promotion QOSes applied to it) in several contexts:

  • When the operation is added to a queue, or when the qualityOfService property of the queue the operation is in is changed
    • (as discussed in the NSOperationQueue section)
  • When a different operation is added to a queue that the operation (in question) is already in
    • (as discussed in the NSOperationQueue section)
  • When a different later (after the operation in question) operation in the same queue has its effective QOS raised
    • the effective QOS of the other operation promotes the operation
  • When a different operation (a dependee) becomes dependent upon the operation in question
    • the effective QOS of the dependee promotes the operation
  • When a dependee operation has its effective QOS raised
    • the new effective QOS of the dependee promotes the operation
  • When the operation is waited upon, with the -waitUntilFinished method, or indirectly when the operation's queue's -waitUntilAllOperationsAreFinished method, is used
    • if the waiting thread is the main thread, the promotion QOS is taken to be NSQualityOfServiceUserInteractive;
    • otherwise if the waiting is done in the execution context of another operation, its effective QOS promotes the operation;
    • otherwise the QOS of the current thread promotes the operation.

These are all collectively called the promotion QOSes; or for the MAX() of all of them, just promotion QOS.

These various values are put together into the effective QOS. The effective QOS is the MAX() of all of these QOS values: {the inferred QOS, the promotion QOSes, the qualityOfService property value}, with these qualifications:

  • If the operation's qualityOfService property has been explicitly set to anything, even NSQualityOfServiceDefault, the inferred QOS is ignored.
  • If the operation's qualityOfService property has not been explicitly set to anything, it is ignored (as if no value exists).
  • All QOS values of NSQualityOfServiceDefault are ignored.
  • If there are no QOS values after all that ignoring, the effective QOS is NSQualityOfServiceDefault.

Thus, for example, if an operation is waited upon, its effective QOS may be raised by the waiting context, which may recursively raised all of its dependent operations and all operations ahead of it in the queue (and so on recursively outward in the tree of these relationships).

An operation's qualityOfService property value has no effect if the operation is started manually, rather than being put in an NSOperationQueue, unless the code which is starting it reads out that value and makes some appropriate use of it; that is outside the purview of Foundation.