Reputation: 2171
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
Reputation: 2194
Avoid completion blocks - they're out-of-queue mechanism unsuitable for synchronising any operations or their communications.
Introducing dependency (B depends on A) means B will only run AFTER A has completed successfully.
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
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
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