2

Why does the print("2") part never get called in the following code? I'd think that the inner main.async would push the block into the main loop's queue, and then RunLoop.run would execute it, but apparently that isn't what happens. (It prints 1, run, run, run, etc.)

Also, if I remove the outer main.async, and just directly run the code in that block (still on the main queue, in viewDidLoad of a new single-view app), then the inner main.async block does get executed (prints 1, run, 2). Why does this change make such a difference?

var x = -1
DispatchQueue.main.async {   //  comment out this line for question #2
    print("1")
    x = 1
    DispatchQueue.main.async {
        print("2")
        x = 2
    }
    while x == 1 {
        print("run")
        RunLoop.main.run(mode: .default, before: Date() + 1)
    }
}   //  comment out this line for question #2
Rob
  • 415,655
  • 72
  • 787
  • 1,044
imre
  • 1,667
  • 1
  • 14
  • 28
  • 1
    FWIW, that kludgy technique of spinning on a run loop is a bit of an anachronism. We don’t see it very often nowadays. It was generally used when someone tried to make an inherently asynchronous process behave synchronously, and used this run loop technique to at least let the run loop to process events. But as you’ve discovered, it isn’t ideal and can introduce these sorts of issues. Plus, there are generally better, more efficient solutions (e.g., embrace asynchronous patterns rather than fighting them). – Rob Jun 27 '20 at 05:16
  • 1
    @Rob I know, I know. The use case where I'm trying to do something similar is basically a debug tool that kind of replaces normally sync calls with networked RPC calls, and ideally it would be nice if I could use it without having to modify the caller. – imre Jun 27 '20 at 05:21
  • OK. But I had to say it, for the sake of future readers at the very least. Too many new devs unfamiliar with async patterns tend to latch onto these sorts of work-arounds. No offense intended. – Rob Jun 27 '20 at 05:24

1 Answers1

5

In your first example, the first async blocks the main serial queue until it returns from that outer async call, something that won’t happen while x is 1. That inner GCD async task (that updates x to 2) will never have a chance to run since that serial GCD queue is now blocked in that while loop. The attempt to run on the main run loop does not circumvent the rules/behavior of GCD serial queues. It only drains the run loop of events that have been added to it.

In your second example, you haven’t blocked the GCD main queue, so when you hit run, the dispatched block that updates x to 2 does have a chance to run, letting it proceed.

Bottom line, don’t conflate the GCD main queue and the main run loop. Yes, they both use the main thread, but run loops can’t be used to circumvent the behavior of serial GCD queues.

Rob
  • 415,655
  • 72
  • 787
  • 1,044