slider
slider

Reputation: 2816

GCD functions called out of order

I can't figure out why the following code is being called out of order. It's only written out of order to expose the order in which they're called. This is important because I'm using these with web service calls that are supposed to wait on one another, and they're not. So, it may just be my conception of how GCD is used.

var group: dispatch_group_t = dispatch_group_create()
var groupTwo: dispatch_group_t = dispatch_group_create()
var queue: dispatch_queue_t = dispatch_get_main_queue()

dispatch_group_notify(groupTwo, queue) { () -> Void in
     print("3rd") // Should be called 3rd
}

dispatch_group_enter(group)
     print("1st") // Should be called 1st
dispatch_group_leave(group)

dispatch_group_notify(group, queue) { () -> Void in
     dispatch_group_enter(groupTwo)
          print("2nd") // Should be called 2nd
     dispatch_group_leave(groupTwo)
}

This is the order it's printing in:

1st
3rd
2nd

Why is the order wrong? Shouldn't 3rd only get called once dispatch_group_leave(groupTwo) is called? Why is it getting called beforehand? I thought that was what dispatch_group_notify() was used for.

EDIT: Sorry, I just fixed the group names. Forgot to edit some at first.

Upvotes: 1

Views: 115

Answers (2)

Carlos
Carlos

Reputation: 1176

It sounds like you need to wait for the result of two or more remote calls before you can issue a final remote call. Let's say you have two properties for the parameters, which will be populated by remote calls:

var a:String? = nil // populated asynchronously
var b:String? = nil // populated asynchronously

Then, let's say your remote calls look like this:

func getParameterA( completionHandler:( String ) -> Void ) {
    print( "Getting parameter A" )
    dispatch_async( dispatch_get_global_queue( QOS_CLASS_USER_INITIATED, 0 ) ) {
        NSThread.sleepForTimeInterval(0.2)
        completionHandler( "A" )
        print( "Got parameter A" )
    }
}

func getParameterB( completionHandler:( String ) -> Void ) {
    print( "Getting parameter B" )
    dispatch_async( dispatch_get_global_queue( QOS_CLASS_USER_INITIATED, 0 ) ) {
        NSThread.sleepForTimeInterval( 0.1 )
        completionHandler( "B" )
        print( "Got parameter B" )
    }
}

func getResult( a:String, b:String, completionHandler:( String ) -> Void ) {
    dispatch_async( dispatch_get_global_queue( QOS_CLASS_USER_INITIATED, 0 ) ) {
        NSThread.sleepForTimeInterval( 0.05 )
        completionHandler( "This is the result of \(a) and \(b)" )
    }
}

In reality, they will make remote calls instead of sleeping on background threads.

Then, your code to populate a and b will look like this:

// the blocks in the parameter group are responsible for setting a and b
let parameterGroup = dispatch_group_create()
dispatch_group_enter( parameterGroup )
getParameterA() { parameter in
    // set the value of a asynchronously
    self.a = parameter
    dispatch_group_leave( parameterGroup )
}
dispatch_group_enter( parameterGroup )
getParameterB() { parameter in
    // set the value of b asynchronously
    self.b = parameter
    dispatch_group_leave( parameterGroup )
}

Finally, you can use dispatch_group_notify to define a final completion handler that only executes once there are no more tasks in parameterGroup:

let finalGroup = dispatch_group_create()
dispatch_group_enter( finalGroup )
dispatch_group_notify( parameterGroup, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) ) {
    self.getResult( self.a!, b:self.b! ) { result in
        print( "-- \(result)" )
        dispatch_group_leave( finalGroup )
    }
}
dispatch_group_wait( finalGroup, DISPATCH_TIME_FOREVER )

The finalGroup is not strictly necessary, but I needed it to get the example to work inside an XCTest.

The output will look like this:

Getting parameter A
Getting parameter B
Got parameter B
Got parameter A
-- This is the result of A and B

Upvotes: 1

CouchDeveloper
CouchDeveloper

Reputation: 19098

dispatch_group_notify(groupTwo, queue) { () -> Void in
     print("3rd") // Should be called 3rd
}

dispatch_group_notify will submit the first block to the queue when the group is empty. Initially, the group is empty. So, it will be asynchronously submitted to the main queue.

Here

dispatch_group_enter(group)
     print("1st") // Should be called 1st
dispatch_group_leave(group)

you effectively print to the console on the main queue - this prints

1st

And here

dispatch_group_notify(group, queue) { () -> Void in
     dispatch_group_enter(groupTwo)
          print("2nd") // Should be called 2nd
     dispatch_group_leave(groupTwo)
}

you asynchronously submit a second block which gets enqueued after the first block.

Now, the first block executes and prints

3rd

And finally, the second block prints:

2nd

Upvotes: 0

Related Questions