NSOperation dependency and completionBlock

We're having a simple problem regarding NSOperationQueue, here's a simple operation logic:

self.queue = [[NSOperationQueue alloc] init];

NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation A");
    [NSThread sleepForTimeInterval:1.2];
    NSLog(@"- Done operation A");
}];

NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation B");
    [NSThread sleepForTimeInterval:2];
    NSLog(@"- Done operation B");
}];

[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A");
}];

[operationB setCompletionBlock:^{
    NSLog(@"-- Completion Block B");
}];

[operationB addDependency:operationA];
[self.queue addOperations:@[operationA, operationB] waitUntilFinished:NO];

Here is the final output

2015-12-21 14:59:57.463 SampleProject[18046:310901] - Running operation A
2015-12-21 14:59:58.664 SampleProject[18046:310901] - Done operation A
2015-12-21 14:59:58.664 SampleProject[18046:310900] - Running operation B
2015-12-21 14:59:58.664 SampleProject[18046:310904] -- Completion Block A
2015-12-21 15:00:00.736 SampleProject[18046:310900] - Done operation B
2015-12-21 15:00:00.736 SampleProject[18046:310904] -- Completion Block B

As we can see, the operation B is executed before the operation A's completionBlock. In our real application, we have many operation A and only one operation B that is dependant on all operation A. But then the problem we have is that operationB is launched before the last operation A's completion block has been called, which would normally give information to the operation B.

How would I make operation B to execute after all the operation A's completion blocks?

Upvotes: 4

Views: 1490

Answers (3)

Motti Shneor
Motti Shneor

Reputation: 2194

  1. Avoid completion blocks - they're out-of-queue mechanism unsuitable for synchronising any operations or their communications.

  2. Introducing dependency (B depends on A) means B will only run AFTER A has completed successfully.

  3. For this reason - any simple data object can be used to "pass the information" safely between these two operations - that could simply share it (it suffices that you create it outside the blocks defining both operations. When "B" runs, it can ASSUME that "A" has already put needed info in the data object, and simply access it.

    self.queue = [[NSOperationQueue alloc] init];
    
    NSMutableDictionary *info = [NSMutableDictionary new]; // data object for communication
    
    NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"- Running operation A");
     [NSThread sleepForTimeInterval:1.2];
    
      info[@"DoThis"] = @YES;
      info[@"DoThat"] = @NO;
      NSLog(@"- Done operation A");
    }];
    
    NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"- Running operation B");
    
      if ([info[@"DoThis"] boolValue] NSLog(@"Op A said to do this.");
      if ([info[@"DoThat"] boolValue] NSLog(@"Op A said to do that.");
    
      [NSThread sleepForTimeInterval:2];
      NSLog(@"- Done operation B");
    }];
    
    [operationB addDependency:operationA];
    [self.queue addOperations:@[operationA, operationB] waitUntilFinished:NO];
    

Upvotes: 0

alexkent
alexkent

Reputation: 1596

As you have found in your testing completion blocks are not 'part of the queue' instead they run outside of the Operation Queue (and on another thread). Thus Operation A's completionBlock will run at the same time (ish) as Operation B.

I suggest you refactor your code to remove all the completion blocks.

You say you are using the completionBlocks to pass information from operation A's to B, there are two options for this: Give B references to all the A's (not weak) so when B runs it can pick the results from all the A's. Or if keeping all the A's around until B runs is infeasible for some reason, then recreate your completionBlock's as another NSOperation:

NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    // do stuff
}];

NSOperation *operationATail = [NSBlockOperation blockOperationWithBlock:^{
    // do completionBlock stuff
}];

[operationATail addDependency:operationA];
[operationB addDependency:operationATail];
[self.queue addOperations:@[operationA, operationATail, operationB] waitUntilFinished:NO];

Upvotes: 5

Reedy
Reedy

Reputation: 2076

why can you not invoke the operation inside of completion block a, thats what a completion block is there for.

[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A");
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"- Running operation B");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"- Done operation B");
    }];
    [queue addOperations:@[operationB] waitUntilFinished:NO];
}];


[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A when we dont need B");
}];

There is some nicer ways to perform this instead by using

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     [self operationB];
}

Upvotes: 0

Related Questions