Sti
Sti

Reputation: 8484

Can calls from multiple BackgroundThreads to MainThread happen at the same time?

There are probably a lot of better ways to solve this with a completely different approach, but humor me. I'd like to know this exact situation.

Here I have two functions, doSomething and doSomethingElse. They are called from doEverything(), and both return a completionBlock that will trigger async.

The goal is to call everythingDone() as soon as both these async calls are completed. As I said, there are probably better ways of solving this, but I'd like to know what can happen with this exact logic.

In both completions, I check if both completions have completed, and then call everythingDone if it is.

func doSomething(completion:((Int)->())?){
    DispatchQueue.global(qos: .background).async {
        completion?(123)
    }
}
func doSomethingElse(completion:((String)->())?){
    DispatchQueue.global(qos: .background).async {
        completion?("Test")
    }
}

func doEverything(){
    var values:[Any] = []
    var somethingDone:Bool = false
    var somethingElseDone:Bool = false

    doSomething { (value) in
        DispatchQueue.main.async {
            values.append(value)
            somethingDone = true
            if somethingDone && somethingElseDone{
                self.everythingDone(values: values)
            }
        }
    }
    doSomethingElse { (value) in
        DispatchQueue.main.async {
            values.append(value)
            somethingElseDone = true
            if somethingDone && somethingElseDone{
                self.everythingDone(values: values)
            }
        }
    }
}

func everythingDone(values:[Any]){
    print("Everything done: ", values)
}

Can everythingDone ever happen twice? Is there a slight possibility that the order of events can cause this:

  1. DoSomething-completion sets somethingDone=true
  2. DoSomethingElse-completion sets somethingElseDone = true
  3. DoSomething-completion checks if both is true, which they are
  4. DoSomethingElse-completion checks if both is true, which they are.

Can this happen? Can the main.asyc call happen "intertwined"?

Upvotes: 0

Views: 76

Answers (1)

arturdev
arturdev

Reputation: 11039

Short answer:
NO. The everythingDone can't be called twice.

Long answer:
The main queue (DispatchQueue.main) is a serial queue, which means that tasks will be finished one by one, and the second DispatchQueue.main.async closure will wait until the first one finishes its job.

A little demonstration:
Imagine the following code:

    DispatchQueue.global(qos: .background).async {
        DispatchQueue.main.async {
            //Closure A
            for i in 0..<10 {
                print("a\(i)")
            }
        }
    }


    DispatchQueue.global(qos: .background).async {
        DispatchQueue.main.async {
            //Closure B
            for i in 0..<10 {
                print("b\(i)")
            }
        }
    }

If you run this code, in the console you'll see the following result:

a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9

As you can see, at first it runs and finishes the code inside Close A, and only after it starts and finishes the code inside Closure B.

But, if we modify our code a little bit (by moving closure directly to global queue:

    DispatchQueue.global(qos: .background).async {
        //Closure A
        for i in 0..<10 {
            print("a\(i)")
        }
    }


    DispatchQueue.global(qos: .background).async {   
        //Closure B         
        for i in 0..<10 {
            print("b\(i)")
        }
    }

The result will be the following:

b0 a0 b1 b2 b3 b4 b5 a1 b6 a2 b7 a3 b8 a4 a5 a6 a7 a8 a9 b9

Here you can see that the order is broken and it is even unpredictable, and it may change on every execution. This is because DispatchQueue.global(qos: .background) is concurrent queue, so tasks will be performed simultaneously and will be finished on unexpected schedules.

So as soon as your closures are in a serial queue (in your case it is main queue) then the answer is NO, otherwise, the answer is YES.

Upvotes: 5

Related Questions