Target Parameter in Dispatchqueue

Is it possible to specify the `DispatchQueue` for `DispatchQueue.concurrentPerform`?

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:

Sample Image


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:

Sample Image


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

How does a DispatchQueue work? (specifically multithreading)


Why ask this?

This is a fairly broad question about the internals of grand-central-dispatch. I had difficulty understanding the dumped output because the original WWDC '10 videos and slides for GCD are no longer public. I also didn't know about the open-source libdispatch repo (thanks Rob). That needn't be a problem, but there are no related QAs on SO explaining the topic in detail.

Why GCD?

According to the WWDC '10 GCD transcripts (Thanks Rob), the main idea behind the API was to simplify the boilerplate associated with using the #selector API for multithreading.

Benefits of GCD

Apple released a new block-based API instead of going with function pointers, to also enable type-safe code that wouldn't crash if the block had the wrong type signature. Using typedefs also made code cleaner when used in function parameters, local variables and @property declarations. Queues allow you to capture code and some state as a chunk of data that get managed, enqueued and executed automatically behind the scenes.

The same session mentions how GCD manages low-level threads under the hood. It enqueues blocks to execute on threads when they need to be executed and then releases those threads (PThreads to be precise) when they are no longer referenced. GCD manages threads automatically and doesn't expose this API - when a DispatchWorkItem is dequeued GCD creates a thread for this block to execute on.

Drawbacks of performSelector

performSelector:onThread:withObject:waitUntilDone: has numerous drawbacks that suggest poor design for the modern challenges of concurrency, waiting, synchronisation. leads to pyramids of doom when switching threads in a func. Furthermore, the NSObject.performSelector family of threading methods are inflexible and limited:

  1. No options to optimise for concurrent, initially inactive, or synchronisation on a particular thread. Unlike GCD.
  2. Only selectors can be dispatched on to new threads (awful).
  3. Lots of threads for a given function leads to messy code (pyramids of doom).
  4. No support for queueing without a limited (at the time when GCD was announced in iOS 4) NSOperation API. NSOperations are a high-level, verbose API that became more powerful after incorporating elements of dispatch (low-level API that became GCD) in iOS 4.
  5. Lots of bugs related to unhandled invalid Selector errors (type safety).

DispatchQueue internals

I believe the xref, ref and sref are internal registers that manage reference counts for automatic reference counting. GCD calls dispatch_retain and dispatch_release in most cases when needed, so we don't need to worry about releasing a queue after all its blocks have been executed. However, there were cases when a developer could call retain and release manually when trying to ensure the queue is retained even when not directly in use. These registers allow libDispatch to crash when release is called on a queue with a positive reference count, for better error handling.

When calling a block with DispatchQueue.global().async or similar, I believe this increments the reference count of that queue (xref and ref).

The variables in the question are not documented explicitly, but from what I can tell:

  • xref counts the number of external references to a general DispatchQueue.
  • ref counts the total number of references to a general DispatchQueue.
  • sref counts the number of references to API serial/concurrent/runloop queues, sources and mach channels (these need to be tracked differently as they are represented using different types).

in-barrier looks like an internal state flag (DispatchWorkItemFlag) to track whether new work items submitted to a concurrent queue should be scheduled or not. Only once the barrier work item finishes, the queue returns to scheduling work items that were submitted after the barrier. in-flight means that there is no barrier in force currently.

state is also not documented explicitly but I presume points to memory where the block can access variables from the scope where the block was scheduled.

How to create dispatch queue in Swift 3

Creating a concurrent queue

let concurrentQueue = DispatchQueue(label: "queuename", attributes: .concurrent)
concurrentQueue.sync {

}

Create a serial queue

let serialQueue = DispatchQueue(label: "queuename")
serialQueue.sync {

}

Get main queue asynchronously

DispatchQueue.main.async {

}

Get main queue synchronously

DispatchQueue.main.sync {

}

To get one of the background thread

DispatchQueue.global(qos: .background).async {

}

Xcode 8.2 beta 2:

To get one of the background thread

DispatchQueue.global(qos: .default).async {

}

DispatchQueue.global().async {
// qos' default value is ´DispatchQoS.QoSClass.default`
}

If you want to learn about using these queues .See this answer

How to dispatch a block with parameter on main queue or thread

Nope. Wrapping blocks is exactly what you have to do in this case. In code:

void (^block)(id someArg) = someBlock;
id object = someObject;
dispatch_async(dispatch_get_main_queue(), ^{
block(someObject);
});

It may look a little strange at first, but this style makes the dispatch APIs so much simpler and the automatic retaining of captured variables makes it possible. I'm a little surprised you ran into problems. What were they?

GCD dispatch_set_target_queue function's 1st parameter type

You can think of dispatch_object_t as the "base class" of all the dispatch object types.

In "plain" C this uses the transparent union GCC extension, which essentially allows all pointer types in the union to be treated interchangeably with the union type when used as a function argument.

the macro below the block you quoted from dispatch/object.h explains the connection with dispatch_queue_t:

#define DISPATCH_DECL(name) typedef struct name##_s *name##_t

and then later on in dispatch/queue.h

DISPATCH_DECL(dispatch_queue);

i.e. dispatch_queue_t matches the _dq member of the transparent union and hence is a valid type to pass to the dispatch_object_t argument of dispatch_set_target_queue.

FWIW in Objective-C and C++ the dispatch_object_t superclass relationship is expressed using the respective object type system, c.f. the other sections in the dispatch_object_t area of dispatch/object.h.



Related Topics



Leave a reply



Submit