Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSOperation - Forcing an operation to wait others dynamically

I am trying to implement an operation queue and I have the following scenario:

NSOperation A NSOperation B NSOperation C NSOperation D NSOperationQueue queue 

I start adding A to queue.

During the execution of A I need to get some data from B and I can't continue with A until B returns what I need.

The same situation will occur for B depending on C and for C depending on D.

To manage this, at each NSOperation I have this code:

NSOperation *operation; //This can be A, B, C, D or any other NSOperation  [self setQueuePriority:NSOperationQueuePriorityVeryLow]; //Set the current NSOperation with low priority  [queue addOperation: operation]; //Add the operation that I want to the queue  while(!operation.isFinished && !self.isCancelled){} //I need to wait the operation that I depend before moving on with the current operation  [self setQueuePriority:NSOperationQueuePriorityNormal]; //After the while, the other operation finished so I return my priority to normal and continue  if(self.isCancelled){ //If I get out of the while because the current operation was cancelled I also cancel the other operation. [operation cancel];           } 

My problem is that when I have something like 3 or 4 NSOperations waiting and executing the while(!operacao.isFinished && !self.isCancelled){} my code just freeze because the NSOperation that is important to me don't get executed, even if it have higher priority.

What I tried

  • Adding dependency during execution time but since my NSOperation is already running I doesn't seems to have any effect.

  • Instead of adding the operation to queue, I can do something [operation start]. It works, but canceling the current operation will also cancel the other operations that I started?

  • I can do something like while(!operacao.isFinished && !self.isCancelled){[NSThread sleepForTimeInterval:0.001];}. It works, but is this the correct way? Maybe there is a better solution.

In this situation how I can guarantee that the operation that I want will run and the others will wait in background? What is the correct way to solve this?

If anyone question me why I don't add the dependency before starting my queue its because an operation will need the other only if some conditions are true. I will know if I need other operation only during execution time.

Thanks for your time.

like image 897
Rafael Avatar asked Dec 07 '12 11:12

Rafael


People also ask

What is the difference between GCD and NSOperation?

GCD is a low-level C-based API that enables very simple use of a task-based concurrency model. NSOperation and NSOperationQueue are Objective-C classes that do a similar thing. NSOperation was introduced first, but as of 10.5 and iOS 2, NSOperationQueue and friends are internally implemented using GCD .

What is NSOperation queue?

A queue that regulates the execution of operations.

What is NSOperation in Swift?

An abstract class that represents the code and data associated with a single task.


2 Answers

Here's two ideas for you with contrived examples. I only used two operations but you could expand the concept to any number and/or nest them as needed.

Example 1: Using Grand Central Dispatch

GCD provides lightweight "dispatch groups", which allow you to explicitly order tasks and then wait on their completion. In this case AlphaOperation creates a group and enters it, then starts BetaOperation, whose completionBlock causes the group to be left. When you call dispatch_group_wait, the current thread blocks until the number of times entering the group is equal to the number of times leaving it (a lot like retain count). Don't forget to check the isCancelled state of the operation after any potentially long-running task.

@interface BetaOperation : NSOperation @end @implementation BetaOperation - (void)main {     NSLog(@"beta operation finishing"); } @end  @interface AlphaOperation : NSOperation @end @implementation AlphaOperation - (void)main {     dispatch_group_t group = dispatch_group_create();     dispatch_group_enter(group);      BetaOperation *betaOperation = [[BetaOperation alloc] init];     betaOperation.completionBlock = ^{         dispatch_group_leave(group);     };      [betaOperation start];      dispatch_group_wait(group, DISPATCH_TIME_FOREVER);      if ([self isCancelled])         return;      NSLog(@"alpha operation finishing"); } @end  @implementation AppDelegate  - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {     dispatch_async(dispatch_get_main_queue(), ^{         AlphaOperation *operation = [[AlphaOperation alloc] init];         [operation start];     });      return YES; }  @end 

Example 2: Using a local NSOperationQueue

Since you're already with working operations, another option is creating a queue as a property of AlphaOperation, then adding BetaOperation and calling waitUntilAllOperationsAreFinished on the queue. This has an added benefit in that you can easily cancel the queue's operations when AlphaOperation is cancelled, simply by overriding the cancel method.

@interface BetaOperation : NSOperation @end @implementation BetaOperation - (void)main {     NSLog(@"beta operation finishing"); } @end  @interface AlphaOperation : NSOperation @property (strong) NSOperationQueue *queue; @end @implementation AlphaOperation - (void)main {     self.queue = [[NSOperationQueue alloc] init];      BetaOperation *betaOperation = [[BetaOperation alloc] init];     [self.queue addOperation:betaOperation];     [self.queue waitUntilAllOperationsAreFinished];      if ([self isCancelled])         return;      NSLog(@"alpha operation finishing"); }  - (void)cancel {     [super cancel];      [self.queue cancelAllOperations]; } @end  @implementation AppDelegate  - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {     dispatch_async(dispatch_get_main_queue(), ^{         AlphaOperation *operation = [[AlphaOperation alloc] init];         [operation start];     });      return YES; }  @end 
like image 158
roland Avatar answered Sep 29 '22 11:09

roland


One approach is to manage this from outside the operation classes ie. setup the operation dependencies between A/B/C/D correctly while creating them.

Steps: (In the method that is creating these operations)

1) Create Operation A

2) If data provided by Operation B is not available, create Operation B, and make Operation A dependent on Operation B. ie. something like operationA.addDependency(operationB);

3). repeat step 2 for C and D. (ie. B depends on C and C depends on D, if required)

4) Add the operations to queue. The queue will execute based on the dependencies ie. D, C, B, A.

like image 43
user1885297 Avatar answered Sep 29 '22 09:09

user1885297