1

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.

Kartick Vaddadi
  • 4,818
  • 6
  • 39
  • 55

2 Answers2

2

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks. That makes sense. I'll watch those WWDC sessions. Note that I do want to test with the main queue, because I do want tasks to execute on the main queue but in priority order. Testing with a different queue won't be representative of what I want to do. Perhaps I'll build my own layer on top of DispatchQueue :) – Kartick Vaddadi Sep 08 '17 at 07:32
  • QoS doesn't affect the order of items on a particular serial queue (and the main queue is a serial queue). Watch those videos and it should become a little more clear. If you still have questions after that, post a separate question, perhaps showing us the broader problem you're trying to solve (because, with absolutely no offense intended, the idea of scheduling so many things on the main queue (which should never be blocked) such that priority would make any difference suggests a deeper design problem; if we knew what problem you were trying to solve, we could advise you better). – Rob Sep 08 '17 at 08:00
  • Got it. I've downloaded the video to watch later. I'll try an OperationQueue, which does support priority. No offense taken, and I appreciate your thoughts. The main thread won't be blocked if each task finishes quickly: a single 1s-long operation on the main queue is a problem. 100 operations each 10ms long aren't a problem, as long as these are queued behind more important tasks, like UI events. That way, I get the benefit of threading without synchronisation and race conditions. The UI remains responsive in my tests, so no problem. None of my beta testers flagged this as an issue, either. – Kartick Vaddadi Sep 08 '17 at 09:46
  • I also log the time I'm blocking the main thread, so I know if things have gotten worse. – Kartick Vaddadi Sep 08 '17 at 09:50
2

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.

Kartick Vaddadi
  • 4,818
  • 6
  • 39
  • 55