Why can’t we use a dispatch_sync on the current queue?

dispatch_sync does two things:

  1. queue a block
  2. blocks the current thread until the block has finished running

Given that the main thread is a serial queue (which means it uses only one thread), if you run the following statement on the main queue:

dispatch_sync(dispatch_get_main_queue(), ^(){/*...*/});

the following events will happen:

  1. dispatch_sync queues the block in the main queue.
  2. dispatch_sync blocks the thread of the main queue until the block finishes executing.
  3. dispatch_sync waits forever because the thread where the block is supposed to run is blocked.

The key to understanding this issue is that dispatch_sync does not execute blocks, it only queues them. Execution will happen on a future iteration of the run loop.

The following approach:

if (queueA == dispatch_get_current_queue()){
    block();
} else {
    dispatch_sync(queueA, block);
}

is perfectly fine, but be aware that it won’t protect you from complex scenarios involving a hierarchy of queues. In such case, the current queue may be different than a previously blocked queue where you are trying to send your block. Example:

dispatch_sync(queueA, ^{
    dispatch_sync(queueB, ^{
        // dispatch_get_current_queue() is B, but A is blocked, 
        // so a dispatch_sync(A,b) will deadlock.
        dispatch_sync(queueA, ^{
            // some task
        });
    });
});

For complex cases, read/write key-value data in the dispatch queue:

dispatch_queue_t workerQ = dispatch_queue_create("com.meh.sometask", NULL);
dispatch_queue_t funnelQ = dispatch_queue_create("com.meh.funnel", NULL);
dispatch_set_target_queue(workerQ,funnelQ);

static int kKey;
 
// saves string "funnel" in funnelQ
CFStringRef tag = CFSTR("funnel");
dispatch_queue_set_specific(funnelQ, 
                            &kKey,
                            (void*)tag,
                            (dispatch_function_t)CFRelease);

dispatch_sync(workerQ, ^{
    // is funnelQ in the hierarchy of workerQ?
    CFStringRef tag = dispatch_get_specific(&kKey);
    if (tag){
        dispatch_sync(funnelQ, ^{
            // some task
        });
    } else {
        // some task
    }
});

Explanation:

  • I create a workerQ queue that points to a funnelQ queue. In real code this is useful if you have several “worker” queues and you want to resume/suspend all at once (which is achieved by resuming/updating their target funnelQ queue).
  • I may funnel my worker queues at any point in time, so to know if they are funneled or not, I tag funnelQ with the word “funnel”.
  • Down the road I dispatch_sync something to workerQ, and for whatever reason I want to dispatch_sync to funnelQ, but avoiding a dispatch_sync to the current queue, so I check for the tag and act accordingly. Because the get walks up the hierarchy, the value won’t be found in workerQ but it will be found in funnelQ. This is a way of finding out if any queue in the hierarchy is the one where we stored the value. And therefore, to prevent a dispatch_sync to the current queue.

If you are wondering about the functions that read/write context data, there are three:

  • dispatch_queue_set_specific: Write to a queue.
  • dispatch_queue_get_specific: Read from a queue.
  • dispatch_get_specific: Convenience function to read from the current queue.

The key is compared by pointer, and never dereferenced. The last parameter in the setter is a destructor to release the key.

If you are wondering about “pointing one queue to another”, it means exactly that. For example, I can point a queue A to the main queue, and it will cause all blocks in the queue A to run in the main queue (usually this is done for UI updates).

Leave a Comment