Can you use cancel / isCancelled with GCD / dispatch_async?

I was wondering if you can use cancel / cancelAllOperations / .isCancelled with the thread that you started with GCD?

Currently, I just use a boolean as a flag to cancel the background process.

Suppose you want to do a lot of processing in the background while maintaining the user interface so that you can catch the cancel button (or revive something to show that the processor is working). Here's how we do it ...

@interface AstoundingView : UIView { BOOL pleaseAbandonYourEfforts; blah } @implementation AstoundingView // // these are the foreground routines... // begin, abandon and all-done // -(void)userHasClickedToBuildASpaceship { [YourUIStateMachine buildShip]; [self procedurallyBuildEnormousSpaceship]; } -(void)userHasClickedToAbandonBuildingTheSpaceship { [YourUIStateMachine inbetween]; pleaseAbandonYourEfforts = false; // that it! } -(void)attentionBGIsAllDone { // you get here when the process finishes, whether by completion // or if we have asked it to cancel itself. [self typically setNeedsDisplay, etc]; [YourUIStateMachine nothinghappening]; } // // these are the background routines... // the kickoff, the wrapper, and the guts // // The wrapper MUST contain a "we've finished" message to home // The guts can contain messages to home (eg, progress messages) // -(void)procedurallyBuildEnormousSpaceship { // user has clicked button to build new spaceship pleaseAbandonYourEfforts = FALSE; dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self actuallyProcedurallyBuildInBackground]; } ); // as an aside, it worth noting that this does not work if you // use the main Q rather than a global Q as shown. // Thus, this would not work: // dispatch_async(dispatch_get_main_queue(), ^{ ...; }); } -(void)actuallyProcedurallyBuildInBackground { // we are actually in the BG here... [self setUpHere]; // set up any variables, contexts etc you need right here // DO NOT open any variables, contexts etc in "buildGuts" // when you return back here after buildGuts, CLEAN UP those // variables, contexts etc at this level. // (using this system, you can nest as deep as you want, and the // one CHECKER pseudocall will always take you right out. // You can insert CHECKERs anywhere you want.) [self buildGuts]; // Note that any time 'CHECKER' "goes off', you must fall- // through to exactly here. This is the common fall-through point. // So we must now tidy-up, to match setUpHere. [self wrapUpHere]; // when you get to here, we have finished (or, the user has cancelled // the background operation) // Whatever technique you use, // MAKE SURE you clean up all your variables/contexts/etc before // abandoning the BG process. // and then you must do this...... // we have finished. it critical to let the foreground know NOW, // or else it will sit there for about 4 to 6 seconds (on 4.3/4.2) // doing nothing until it realises you are done dispatch_sync( dispatch_get_main_queue(), ^{[self attentionBGIsAllDone];} // would setneedsdisplay, etc ); return; } -(void)buildGuts { // we are actually in the BG here... // Don't open any local variables in here. CHECKER [self blah blah]; CHECKER [self blah blah]; CHECKER [self blah blah]; // to get stuff done from time to time on the UI, something like... CHECKER dispatch_sync( dispatch_get_main_queue(), ^{[supportStuff pleasePostMidwayImage: [UIImage imageWithCGImage:halfOfShip] ];} ); CHECKER [self blah blah]; CHECKER [self blah blah]; CHECKER [self blah blah]; for ( i = 1 to 10^9 ) { CHECKER [self blah blah]; } CHECKER [self blah blah]; CHECKER [self blah blah]; CHECKER [self blah blah]; return; } 

and CHECKER does nothing but verify that the flag is true ...

 #define CHECKER if ( pleaseAbandonYourEfforts == YES ) \ {NSLog(@"Amazing Interruption System Working!");return;} 

It all works great.

But ........ Is it possible to use cancel / cancelAllOperations / .isCancelled with this type of GCD usage?

What is the story here? Greetings.


PS - for beginners using this six-part background template.

Note that when BJ stands out below, whenever you exit the bg process ...

you must clear any public variables!

In my idiom, you should highlight all the variables, contexts, memory, etc., particularly in "setUpHere". And you should let them go in "wrapUpHere". (This idiom continues to work if you are deeper and deeper, while in BG.)

Alternatively, do what BJ shows in your example. (If you use the BJ method, be careful if you go deeper.)

Whatever method you use, you must clear any variables / contexts / memory that you opened when you exit the BG process. Hope someday this helps someone!

+7
source share
2 answers

GCD does not have built-in support for cancellation; if it is important to be able to cancel background tasks, then checking the flag, as you showed, is an acceptable solution. However, you can appreciate how quickly cancellation requires an answer; if some of these method calls are quite short, you can get away with checking less often.

You asked if you can use NSOperation flags to support undo. The answer is no. GCD is not based on NSOperation. In fact, in Snow Leopard, NSOperation and NSOperationQueue were reimplemented for internal GCD use. So addiction is the other way around. NSOperation is a higher-level design than GCD. However, even if you use NSOperation, your cancellation implementation will be basically the same; you still have to periodically check self.isCancelled to see if you should abandon the spacecraft design.

The only concern I have when implementing the CHECKER macro is that it implements an unexpected return . Therefore, you must be careful with memory leaks. If you configured your own NSAutoreleasePool in a background thread, you need to drain it before returning. If you specified alloc ed or retain any objects, you may need to release them before returning.

Since all this cleanup must be performed on every check, you may want to go to one return point. One way to do this is to wrap each call of your method in an if (pleaseAbandonYourEfforts == NO) { } block. This will allow you to quickly go to the end of the method after the cancellation was requested, and save the cleanup in one place. Another option, although some may not like this, would be to make the call to use the goto cleanup; macro goto cleanup; and define the label cleanup: at the end of the method in which you release everything that should be released. Some people don't like using goto almost religious way, but I found that an advanced switch to a cleaning shortcut like this is often a cleaner solution than alternatives. If you do not like this, then all this is done in the if block.


Edit

I feel the need for further clarification of my previous statement on the existence of a single point of return. With the CHECKER macro, as defined above, the -buildGuts method can return at any point where this macro is used. If there are any stored objects in this method, they must be cleaned before returning. For example, imagine this very reasonable change for your -buildGuts method:

 -(void)buildGuts { // we are actually in the BG here... NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; CHECKER [self blah blah]; CHECKER [self blah blah]; CHECKER [self recordSerialNumberUsingFormatter:formatter]; // ... etc ... [formatter release]; return; } 

Please note that in this case, if the CHECKER macro forces us to return to the end of the method, then the object in formatter will not be released and will be skipped . Although the call [self quickly wrap up in a bow] can handle cleanup for any objects accessible through an instance variable or through a global pointer, it cannot release objects accessible only locally in the buildGuts method. This is why I proposed a goto cleanup implementation that would look like this:

 #define CHECKER if ( pleaseAbandonYourEfforts == YES ) { goto cleanup; } -(void)buildGuts { // we are actually in the BG here... NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; CHECKER [self blah blah]; CHECKER [self blah blah]; CHECKER [self recordSerialNumberUsingFormatter:formatter]; // ... etc ... cleanup: [formatter release]; return; } 

Within this implementation, the formatter will always be released, regardless of when the cancellation occurs.

In short, whenever you create a macro that can make you return from a method, you must be sure that before you return prematurely, all memory management is taken care of. It is difficult to do this with a macro that causes a return.

+18
source

Thanks for the discussion! In my case, I wanted to allow the issuance of a new asynchronous request, which will cancel the previous one, if it has not yet completed. In the above example, I would have to somehow wait for the signal using the attentionBGIsAllDone callback that the issued request was canceled before I can send a new request. Instead, I created a simple logical shell, an instance of which I could associate with an outstanding query:

 @interface MyMutableBool : NSObject { BOOL value; } @property BOOL value; @end @implementation MyMutableBool @synthesize value; @end 

And use an instance for pleaseAbandonYourEfforts . Before I do my dispatch_async (i.e. In procedurallyBuildEnormousSpaceship above), I undo the old request and prepare it for the following:

 // First cancel any old outstanding request. cancelRequest.value = YES; // Now create a new flag to signal whether or not to cancel the new request. MyMutableBool *cancelThisRequest = [[[MyMutableBool alloc] init] autorelease]; self.cancelRequest = cancelThisRequest; 

My unit doing the async task will have to check cancelThisRequest.value , of course.

+1
source

All Articles