10

dispatch_apply takes a dispatch queue as a parameter, which allows you to choose which queue to execute the block on.

My understanding is that DispatchQueue.concurrentPerform in Swift is meant to replace dispatch_apply. But this function does not take a dispatch queue as a parameter. After googling around, I found this GCD tutorial which has this code:

let _ = DispatchQueue.global(qos: .userInitiated)
DispatchQueue.concurrentPerform(iterations: addresses.count) { index in
    // do work here
}

And explains:

This implementation includes a curious line of code: let _ = DispatchQueue.global(qos: .userInitiated). Calling this tells causes GCD to use a queue with a .userInitiated quality of service for the concurrent calls.

My question is, does this actually work to specify the QoS? If so, how?

It would kind of make sense to me that there is no way to specify the queue for this, because a serial queue makes no sense in this context and only the highest QoS really makes sense given that this is a synchronous blocking function. But I can't find any documentation as to why it is possible to specify a queue with dispatch_apply but impossible(?) with DispatchQueue.concurrentPerform.

jjoelson
  • 5,771
  • 5
  • 31
  • 51

1 Answers1

20

The author’s attempt to specify the queue quality of service (QoS) is incorrect. The concurrentPerform uses the current queue’s QoS if it can. You can confirm this by tracking through the source code:

  1. concurrentPerform calls _swift_dispatch_apply_current.

  2. _swift_dispatch_apply_current calls dispatch_apply with 0, i.e., DISPATCH_APPLY_AUTO, which is defined as a ...

    ... Constant to pass to dispatch_apply() or dispatch_apply_f() to request that the system automatically use worker threads that match the configuration of the current thread as closely as possible.

    When submitting a block for parallel invocation, passing this constant as the queue argument will automatically use the global concurrent queue that matches the Quality of Service of the caller most closely.

  3. This can also be confirmed by following dispatch_apply call dispatch_apply_f in which using DISPATCH_APPLY_AUTO results in the call to _dispatch_apply_root_queue. If you keep tumbling down the rabbit hole of swift-corelibs-libdispatch, you’ll see that this actually does use a global queue which is the same QoS as your current thread.

Bottom line, the correct way to specify the QoS is to dispatch the call to concurrentPerform to the desired queue, e.g.:

DispatchQueue.global(qos: .userInitiated).async {
    DispatchQueue.concurrentPerform(iterations: 3) { (i) in
        ...
    }
}

This is easily verified empirically by adding a break point and looking at the queue in the Xcode debugger:

enter image description here


Needless to say, the suggestion of adding the let _ = ... is incorrect. Consider the following:

DispatchQueue.global(qos: .utility).async {
    let _ = DispatchQueue.global(qos: .userInitiated)

    DispatchQueue.concurrentPerform(iterations: 3) { (i) in
        ...
    }
}

This will run with “utility” QoS, not “user initiated”.

Again, this is easily verified empirically:

enter image description here


See WWDC 2017 video Modernizing Grand Central Dispatch for a discussion about DISPATCH_APPLY_AUTO and concurrentPerform.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 2
    Thanks for the detailed answer. I knew the code from the tutorial was probably wrong, but raywenderlich.com tutorials are usually pretty reliable. – jjoelson Feb 20 '19 at 13:23