Search code examples
cocoagrand-central-dispatchdispatch-async

How does dispatch_async nesting work?


I’m trying to understand this common pattern:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        // Background stuff on background thread
        dispatch_async(dispatch_get_main_queue()) {
            // Update UI on main thread
        }
    }

The Apple literature states:

Completion callbacks can be accomplished via nested calls to the dispatch_async() function.

Ok, but I thought the FIFO aspect of dispatch_async was that it guarantees that tasks start in the order submitted. I thought it didn't guarantee that they would complete in any order?

My question is, why does the nested call wait for the completion of the closure/block it's nested in?

If I were to write

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        doThing1()
        doThing2()
        doThing3()
}

would that guarantee that doThing2() would wait until the execution of doThing1() before executing? If so, does this mean that it's equivalent to two subsequent dispatch_sync calls, like this:

dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    doThing1()
}

dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    doThing2()
}

?


Solution

  • Ok, but I thought the FIFO aspect of dispatch_async was that it guarantees that tasks start in the order submitted. I thought it didn't guarantee that they would complete in any order?

    You are correct, but it also depends whether or not this is a serial or a concurrent queue. For serial queues, the order of entry is the order of completion: nothing new will happen until the preceding items in the queue have completed.

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
            doThing1()
            doThing2()
            doThing3()
    }
    

    In that code, it doesn't matter what kind of queue it is. Your code is all inside one block. The order of things in that block is not going to be magically shuffled! That would make nonsense of the entire notion of programming.

    However, none of that gets at what Apple means in the sentence you cite. Nested completion blocks are crucial to how things work, because they mean you can get from the main thread to a background thread, do something, and then, when that has completed, get back on to the main thread and do something further. That happens in order. And this guarantees that you can obey the important rule that you mustn't touch the interface on a background thread. Moreover, in that arrangement, locals from the surrounding block are visible, so data is passed safely down from block to block, as in this example from my book:

    - (void) drawThatPuppy {
        CGPoint center = 
            CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
        dispatch_async(self->_draw_queue, ^{ 
            CGContextRef bitmap = [self makeBitmapContext: self.bounds.size];
            [self drawAtCenter: center zoom: 1 context:bitmap];
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self->_bitmapContext)
                    CGContextRelease(self->_bitmapContext);
                self->_bitmapContext = bitmap;
                [self setNeedsDisplay];
            });
        });
    }