Search code examples
iosswiftmacoscocoagrand-central-dispatch

Confused about Operation and OperationQueue QoS relationship


The OperationQueue documentation states for the qualityOfService property:

This property specifies the service level applied to operation
objects added to the queue

However one can simply check that this is not true by just copy & pasting the code below into a new playground.

import Foundation

let q = OperationQueue()
q.qualityOfService = .userInitiated

print("QUEUE", q.qualityOfService.rawValue)

let op = BlockOperation()
op.addExecutionBlock {
    print("OP", op.qualityOfService.rawValue)
}

q.addOperations([op], waitUntilFinished: true)

You will see that the queues QoS level is 25/.userInitiated and the operations -1/.default.

So does it mean that the operations QoS level is .default in relation to the queues elevanted .userInitiated level, or is it .default, despite the queue having a higher QoS level?

What I actually expect is that these 2 values should be the same.

PS: I need to invoke a Process inside the Operation, which in turn also has a qualityOfService setting that should be the same as the queue/ops.


Solution

  • The operation’s qualityOfService indicates whether the operation, itself, needs to dictate a particular QoS. But -1/.default effectively means that it will just use the QoS for the queue (and thus that of the worker thread that is used). I would not be terribly concerned about the QoS of the operation. What you care about is the QoS of the thread on which it runs:

    let q = OperationQueue()
    q.qualityOfService = .userInitiated
    
    print("CURRENT", Thread.current.qualityOfService.rawValue)     // CURRENT 33
    print("QUEUE", q.qualityOfService.rawValue)                    // QUEUE 25
    
    let op = BlockOperation {
        print("THREAD", Thread.current.qualityOfService.rawValue)  // THREAD 25
    }
    
    q.addOperations([op], waitUntilFinished: false)
    

    As you can see, the QoS for the thread that is running the code is precisely what you would expect it to be.


    If you want, you can see how changing the operation’s QoS to something higher than the queue will affect the QoS of the worker thread upon which it runs. Thus, background QoS queue with no QoS specified for the operation:

    let q = OperationQueue()
    q.qualityOfService = .background
    
    print("CURRENT", Thread.current.qualityOfService.rawValue)    // CURRENT 33
    print("QUEUE", q.qualityOfService.rawValue)                   // QUEUE 9
    
    let op = BlockOperation()
    op.addExecutionBlock {
        print("OP", op.qualityOfService.rawValue)                 // OP -1
        print("THREAD", Thread.current.qualityOfService.rawValue) // THREAD 9
    }
    
    q.addOperations([op], waitUntilFinished: false)
    

    But you can, if you want, specify a particular QoS for the operation, in this case escalating it to a higher QoS:

    let q = OperationQueue()
    q.qualityOfService = .background
    
    print("CURRENT", Thread.current.qualityOfService.rawValue)    // CURRENT 33
    print("QUEUE", q.qualityOfService.rawValue)                   // QUEUE 9
    
    let op = BlockOperation()
    op.qualityOfService = .userInitiated                          // change op’s QoS, and thus the worker thread to a higher QoS, too
    op.addExecutionBlock {
        print("OP", op.qualityOfService.rawValue)                 // OP 25
        print("THREAD", Thread.current.qualityOfService.rawValue) // THREAD 25
    }
    
    q.addOperations([op], waitUntilFinished: false)