Why does DispatchQueue not respect QoS?

1.2k views Asked by At

I'm trying to run multiple tasks on the main queue, some more urgent than others. I'm using DispatchQoS to do this:

func enqueue(_ prio: Int) {
  let qos = DispatchQoS(qosClass: .userInteractive, relativePriority: prio)
  DispatchQueue.main.async(qos: qos) {
    NSLog("\(prio)")
  }
}

for i in 1...5 {
  for prio in -15...0 {
    enqueue(prio)
  }
}

I expected to see five zeros, followed by five -1's, followed by five -2's, and so on.

But I got:

-15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 -15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 -15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 -15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 -15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0

In other words, the dispatch queue is executing tasks in the order they're enqueued, ignoring priority.


I then tried different QoS classes instead of different relativePriority:

func enqueue(_ qos: DispatchQoS) {
  DispatchQueue.main.async(qos: qos) {
    NSLog("\(qos.qosClass)")
  }
}

for i in 1...10 {
  enqueue(.background)
  enqueue(.utility)
  enqueue(.userInitiated)
  enqueue(.userInteractive)
}

But the tasks again executed in the order of enqueueing, ignoring QoS.

How do I get the dispatch queue to respect QoS? My goal is to run some tasks on the main queue at a lower priority than others.

2

There are 2 answers

0
Kartick Vaddadi On BEST ANSWER

The solution turned out to be to use OperationQueue:

let operation = BlockOperation(block: self.doSomething)

operation.queuePriority = .low

OperationQueue.main.addOperation(operation)

Then I can continually execute low priority tasks, one after another, without making the UI unresponsive.

Thanks to Rob for pointing out why DispatchQueues weren't respecting priorities.

4
Rob On

In WWDC 2016 video Concurrent Programming with GCD in Swift 3, they mention that when you add something to a queue with a higher priority, it doesn't "jump the queue", but rather uses the QoS to resolve priority inversions (e.g. it can use it to increase the priority of prior tasks in the queue in an effort to satisfy the high QoS later task, not change the order of the tasks in a serial queue).

Plus, I'd suggest you don't test this sort of stuff with the main queue. That's a special serial queue with a dedicated thread, so it's not really very useful for checking for how QoS affects the scheduling and priorization of tasks. I'd suggest creating your own concurrent queues for testing QoS issues. Also, if you add tasks to concurrent queues, they often have to be substantive enough and numerous enough, to see any discernible patterns.

Finally, I'd encourage you to watch WWDC 2015 video Building Responsive and Efficient Apps with GCD, as it goes into QoS in greater detail. The aforementioned 2016 video is good for Swift 3 specific observations, but it's really building on the QoS discussions from the 2015 video.