Reputation: 8484
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:
somethingDone=true
somethingElseDone = true
Can this happen? Can the main.asyc call happen "intertwined"?
Upvotes: 0
Views: 76
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