DispatchGroup.notify is being called before all instances have left the group

How and why does the dispatchgroup.notify method get called before all the entered instances have left?

I tried adding the dispatchGroup.enter within the same loop and the output is the same.

Answered by DTS Engineer in 802741022

Theoretically it should have exited before anything executes at all.

What environment. was your code running in?

The code I wrote was tested by adding it into the standard app template on the main thread. If you were running in a playground, I think you basically end up replicating a very similar environment. We don't actually "say" this, but I believe the playground basically runs all code inside an implicit runloop, since that ensure that more of our APIs "work".

Note that if you simply add that into a command line tool, what will actually print is JUST:

0
2
4
6
8
10

...as main() will return immediately after #7 below.

If that is the case, why should the first 5 work? The same example as earlier, no block objects associated.

dispatch_group.notify is an async call. Breaking down what actually happens in your code, this is what's actually happening:

  1. You (I did, and I suspect you did as well) are running on the main thread.

  2. You create the DispatchGroup.

  3. You call dispatch_group.notify, passing a block which will be called on the main thread.

  4. dispatch_group.notify notes that the count and schedule your block to fire.

  5. You call "dispatch_group.enter" 10 times.

  6. Your loop runs 10 times:

  • Even numbers call "dispatch_group.leave" and print:
0
2
4
6
8
10
  • Odd numbers call DispatchQueue.main.asyncAfter, scheduling a block.
  1. Your code block snippet, passing control back to the main thread/calling code.

  2. The block filed at #3 runs, printing:

Exited
  1. The code blocks filed at #6 run, printing:
MainAsync  1 count: 7
MainAsync  3 count: 8
MainAsync  5 count: 9
MainAsync  7 count: 10
MainAsync  9 count: 11

Does that all make sense now?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Please post your code in a code block. I’d like to copy it out and run it here, but copying code from a screenshot is challenging.

See Quinn’s Top Ten DevForums Tips for this and other titbits for using DevForums effectively.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


var dispatch_group = DispatchGroup()

dispatch_group.notify(queue: .main) {
    print("Exited")
}

for i in 0...10 {
    dispatch_group.enter()
}

for i in 0...10 {
    if i.isMultiple(of: 2) {
        print(i)
        dispatch_group.leave()
    } else {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            print(i)
            dispatch_group.leave()
        }
    }
}

@DTS Engineer Hope this will suffice

You overlooked a crucial detail in the documentation for "notify(queue:work:)":

If the group is empty (no block objects are associated with the dispatch group), the notification block object is submitted immediately.

If you modify your code to this:

        var dispatch_group = DispatchGroup()
    
        for i in 0...10 {
            dispatch_group.enter()
        }

        dispatch_group.notify(queue: .main) {
            print("Exited")
        }

        
        for i in 0...10 {
            if i.isMultiple(of: 2) {
                print(i)
                dispatch_group.leave()
            } else {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    print("MainAsync ", i)
                    dispatch_group.leave()
                }
            }
        }


...then it does exactly what you were expecting.

0
2
4
6
8
10
MainAsync  1
MainAsync  3
MainAsync  5
MainAsync  7
MainAsync  9
Exited

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

If that is the case, why should the first 5 work? The same example as earlier, no block objects associated.

Theoretically it should have exited before anything executes at all.


var count = 0

dispatch_group.notify(queue: .main) {
    print("Exited")
}


for i in 0...10 {
    dispatch_group.enter()
}

for i in 0...10 {
    if i.isMultiple(of: 2) {
        count += 1
        print(i, "count:", count)
        dispatch_group.leave()
    } else {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            count += 1
            print("MainAsync ", i, "count:", count)
            dispatch_group.leave()
        }
    }
}

The output:

2 count: 2
4 count: 3
6 count: 4
8 count: 5
10 count: 6
Exited
MainAsync  1 count: 7
MainAsync  3 count: 8
MainAsync  5 count: 9
MainAsync  7 count: 10
MainAsync  9 count: 11

Am I missing something?

Accepted Answer

Theoretically it should have exited before anything executes at all.

What environment. was your code running in?

The code I wrote was tested by adding it into the standard app template on the main thread. If you were running in a playground, I think you basically end up replicating a very similar environment. We don't actually "say" this, but I believe the playground basically runs all code inside an implicit runloop, since that ensure that more of our APIs "work".

Note that if you simply add that into a command line tool, what will actually print is JUST:

0
2
4
6
8
10

...as main() will return immediately after #7 below.

If that is the case, why should the first 5 work? The same example as earlier, no block objects associated.

dispatch_group.notify is an async call. Breaking down what actually happens in your code, this is what's actually happening:

  1. You (I did, and I suspect you did as well) are running on the main thread.

  2. You create the DispatchGroup.

  3. You call dispatch_group.notify, passing a block which will be called on the main thread.

  4. dispatch_group.notify notes that the count and schedule your block to fire.

  5. You call "dispatch_group.enter" 10 times.

  6. Your loop runs 10 times:

  • Even numbers call "dispatch_group.leave" and print:
0
2
4
6
8
10
  • Odd numbers call DispatchQueue.main.asyncAfter, scheduling a block.
  1. Your code block snippet, passing control back to the main thread/calling code.

  2. The block filed at #3 runs, printing:

Exited
  1. The code blocks filed at #6 run, printing:
MainAsync  1 count: 7
MainAsync  3 count: 8
MainAsync  5 count: 9
MainAsync  7 count: 10
MainAsync  9 count: 11

Does that all make sense now?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

DispatchGroup.notify is being called before all instances have left the group
 
 
Q