0

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()
}

?

magiclantern
  • 768
  • 5
  • 19

1 Answers1

0

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];
        });
    });
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Might help you to read my book: http://www.apeth.com/iOSBook/ch38.html#_grand_central_dispatch – matt Jul 19 '15 at 16:13
  • Thank you. "The order of things in that block is not going to be magically shuffled! That would make nonsense of the entire notion of programming." Hmm... but what if `doThing1()` has an async background call itself that triggers a long network operation? If it weren't in a block, `doThing2()` wouldn't wait for that operation to complete before firing. Are calls inside a block always serial? – magiclantern Jul 19 '15 at 16:23
  • 1
    also: "The globale queues *are* serial queues" - in your book's chapter you say the opposite: "The built-in global queues (available by calling dispatch_get_global_queue) are concurrent." Has this changed since the writing of the book? – magiclantern Jul 19 '15 at 16:55
  • No, it means I'm an idiot. But there are two of me, so your question should be which one is the idiot, the one who wrote the book or the one who answered the question. :) – matt Jul 19 '15 at 17:11
  • That whole first paragraph (after the quote) is wrong. Not sure what matt was thinking. The global queues are concurrent. The order of entry is ***not*** the order of completion. New stuff **will likely** happen before the preceding items in the queue have completed. – Ken Thomases Jul 19 '15 at 17:11
  • "but what if `doThing1()` has an async background call itself that triggers a long network operation?" In that case, `doThing1()` will return instantly and we proceed to `doThing2()`, leaving the completion of the network operation to some later time. That is the nature of async. But we needn't be in a `dispatch` block for that to be the case. It has nothing to do with this situation; it's true of _any_ situation. And it nevertheless remains true that `doThing2()` will not happen until `doThing1()` returns. – matt Jul 19 '15 at 17:15
  • And that is exactly why, in a _nest_ of `dispatch` calls, each `dispatch` call is usually the last thing in its block, as in the example in my book. – matt Jul 19 '15 at 17:16
  • @matt Hey, it’s a Sunday, no sweat :) So, if the new stuff will **likely** happen before the preceding items have completed, then how can we determine for sure that the nested `dispatch_async` to the main thread is going to happen once the preceding operations are completed? I just don't get it. Sorry, guess I'm the idiot now. Is it because it's a) submitted second and b) there are no async calls within the first call? – magiclantern Jul 19 '15 at 17:19
  • 1
    @kamera, another way to think about it: if `doThing1()` calls `dispatch_async()` (or similar) then what it does is "submit" a task. That submitting process completes before `doThing1()` returns and therefore before `doThing2()` gets called. That doesn't mean the task itself completes before then. If, on the other hand, `doThing1()` were to use `dispatch_sync()`, then it's not just submitting the task, it's waiting for it to run to completion. In that case, `doThing1()` doesn't return and `doThing2()` doesn't get called until the task itself has run to completion. – Ken Thomases Jul 19 '15 at 17:19
  • Because in the example I actually gave, there are _no_ async side effects, and because in every case the `dispatch` is the last thing in its block. – matt Jul 19 '15 at 17:20
  • You can be notified when an async completion block completes, but that would be through a different path of execution; it would be up to that async block to call you back at the end. You could pass in a handler to it to call, to provide a pathway. But that is a completely different question! – matt Jul 19 '15 at 17:21
  • Ok I think one of my basic assumptions may be wrong: is the default order of execution of method calls serial? Without the involvement of explicit threading instructions around the call or within the task, is it safe to assume that one method will execute **after** the one called immediately before it is complete? I've always assumed that you can't rely on that. – magiclantern Jul 19 '15 at 17:24
  • 1
    @kamera You are confusing apples with oranges, perhaps. `doThing1(); doThing2()` always means that _within this thread_ they happen in that order. Now, of course, if this code can execute in _two simultaneous threads_, each could be in a different spot in the sequence. But dispatch blocks also help prevent _that_; they in effect serialize the calls (they are a form of data locking, as my book explains). – matt Jul 19 '15 at 17:26
  • I suggest you stop and read the entire chapter from my book that I pointed you to; it will explain a lot. – matt Jul 19 '15 at 17:27
  • I did just that, and then I re-read this comment thread, then I read the chapter again. I finally get it now, or at least I'm closer to a point where it doens't just feel like "magic." Thanks a lot to both you and @Ken Thomases, I definitely feel like I've learned something major today. – magiclantern Jul 19 '15 at 21:08
  • "where it doens't just feel like "magic."" - GOOD! That's definitely the goal. GCD used properly means _freedom_ from fear and confusion, and that is what my chapter tries to teach by example. – matt Jul 19 '15 at 21:56