Per my understanding of the DispatchQueue docs, and various WWDC videos on the matter, if one creates a queue in the following manner:
let q = DisqpatchQueue(
label: "my-q",
qos: .utility,
target: .global(qos: .userInteractive)
)
then one should expect work items submitted via async()
to effectively run at userInteractive
QoS, as the target queue should provide a 'floor' on the effective QoS value (assuming no additional rules are in play, e.g. higher priority items have been enqueued, submitted work items enforce QoS, etc).
In practice, however, this particular formulation does not appear to function that way, and the 'resolved' QoS value seems to be utility
, contrary to what the potentially relevant documentation suggests. This behavior appears to be inconsistent with other permutations of queue construction, which makes it even more surprising.
Here's some sample code I was experimenting with to check the behavior of queues created in various ways that I would expect to function analogously (in regards to the derived QoS value for the threads executing their work items):
func test_qos_permutations() {
// q1
let utilTargetingGlobalUIQ = DispatchQueue(
label: "qos:util tgt:globalUI",
qos: .utility,
target: .global(qos: .userInitiated)
)
let customUITargetQ = DispatchQueue(
label: "custom tgt, qos: unspec, tgt:globalUI",
target: .global(qos: .userInitiated)
)
// q2
let utilTargetingCustomSerialUIQ = DispatchQueue(
label: "qos:util tgt:customSerialUI",
qos: .utility,
target: customUITargetQ
)
// q3
let utilDelayedTargetingGlobalUIQ = DispatchQueue(
label: "qos:util tgt:globalUI-delayed",
qos: .utility,
attributes: .initiallyInactive
)
utilDelayedTargetingGlobalUIQ.setTarget(queue: .global(qos: .userInitiated))
utilDelayedTargetingGlobalUIQ.activate()
let queues = [
utilTargetingGlobalUIQ,
utilTargetingCustomSerialUIQ,
utilDelayedTargetingGlobalUIQ,
]
for q in queues {
q.async {
Thread.current.name = q.label
let threadQos = qos_class_self()
print("""
q: \(q.label)
orig qosClass: \(q.qos.qosClass)
thread qosClass: \(DispatchQoS.QoSClass(rawValue: threadQos)!)
""")
}
}
}
Running this, I get the following output:
q: qos:util tgt:customSerialUI
orig qosClass: utility
thread qosClass: userInitiated
q: qos:util tgt:globalUI-delayed
orig qosClass: utility
thread qosClass: userInitiated
q: qos:util tgt:globalUI
orig qosClass: utility
thread qosClass: utility
This test suggests that constructing a queue with an explicit qos
parameter and targeting a global queue of nominally 'higher' QoS does not result in a queue that runs its items at the target's QoS. Perhaps most surprisingly is that if the target queue is set after the queue was initialized, you do get the expected 'QoS floor' behavior. Is this behavior expected, or possibly a bug?