你可以用GCD / dispatch_async使用cancel / isCancelled吗?

我一直在想,你可以用你用GCD启动的线程取消/取消所有操作/ .isCancelled吗?

目前,我只是使用一个布尔值作为标志,来取消后台进程。

假设您想在后台执行大量的处理,同时保持UI的响应,以便您可以捕获取消button(或animation显示处理器正在工作)。 以下是我们如何做到的

@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's 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's 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's 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; } 

和CHECKER没有什么比检查它的标志是真实的…

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

这一切都完美的作品。

但…….. 是否可以使用取消/ cancelAllOperations / .isCandlled这种types的使用GCD?

这里有什么故事? 干杯。


PS – 对于任何初学者使用这个“六部分”的背景模板。

请注意,如下BJ突出显示,每当你跳出BG进程…

你必须清理你已经打开的任何variables!

在我的成语中,你必须在“setUpHere”中专门分配所有variables,上下文,内存等。 你必须在“wrapUpH​​ere”中释放它们。 (这个习语继续工作,如果你越来越深入,而在BG。)

或者,BJ在他的例子中显示的是什么。 (如果你使用BJ的方法,要小心,如果你深入。)

不pipe你使用什么方法,当你打破BG进程时,你必须清理你打开的任何variables/上下文/内存。 希望它有助于某人,某个时候!

GCD没有内置的取消支持; 如果能够取消你的后台任务是很重要的,那么检查你所示的一个标志是一个可以接受的解决scheme。 但是,您可能想要评估取消需要多快响应; 如果其中一些方法调用的时间相当短,那么您可能可以不经常检查。

你问是否可以使用NSOperation标志来支持取消。 答案是不。 GCD不基于NSOperation。 事实上,在雪豹NSOperation和NSOperationQueue重新实施内部使用GCD。 所以依赖是相反的。 NSOperation是一个比GCD更高层次的构造。 即使你使用NSOperation,但是你的执行取消也是大致相同的。 你还是要定期检查self.isCancelled ,看看你是否应该放弃太空船build造。

我唯一关心的是如何实现CHECKERmacros,它实现了意想不到的return 。 因此,你必须小心内存泄漏。 如果你已经在后台线程上设置了你自己的NSAutoreleasePool,你需要在返回之前将其drain 。 如果您已经allocretain任何对象,则可能需要在返回之前将其release

由于所有清理工作都需要在每次支票上进行,因此您可能需要考虑朝着单一的退货点行进。 一种方法是将每个方法调用包装在if (pleaseAbandonYourEfforts == NO) { }块中。 一旦请求取消,这可以让您快速完成方法的末尾,并将清理保存在一个位置。 另一个select,虽然有些人可能不喜欢它,但会让macros使用呼叫进行goto cleanup; 并在方法的末尾定义一个cleanup:标签,在那里你释放任何需要释放的东西。 有些人不喜欢用几乎虔诚的方式来使用goto ,但是我发现像这样向前跳转到清理标签通常是比替代scheme更清洁的解决scheme。 如果你不喜欢,那么把所有东西都包裹起来就好了。


编辑

我觉得有必要进一步澄清我之前关于单一回归的说法。 使用上面定义的CHECKERmacros, -buildGuts方法可以在使用该macros的任何位置返回。 如果该方法有本地保留的对象,则必须在返回之前对其进行清理。 例如,想象一下对你的-buildGuts方法进行非常合理的修改:

 -(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; } 

请注意,在这种情况下,如果CHECKERmacros导致我们在方法结束之前返回,那么formatter的对象将不会被释放, 并将被泄漏 。 虽然[self quickly wrap up in a bow]调用可以处理任何通过实例variables或全局指针可访问的对象的清除,但它不能释放只能在buildGuts方法中本地使用的buildGuts 。 这就是为什么我build议执行goto cleanup ,看起来像这样:

 #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; } 

在这个实现中,无论何时取消, formatter都会被释放。

简而言之,无论什么时候你需要一个可以使你从一个方法返回的macros,你都必须非常确定,在你提前返回之前,所有的内存pipe理都已经被处理了。 使用导致返回的macros很难做到这一点。

感谢您的讨论! 在我的情况下,我想允许发出一个新的asynchronous请求,如果它还没有完成,将取消前一个请求。 有了上面的例子,我不得不以某种方式通过attentionBGIsAllDonecallback等待一个信号,这个未完成的请求被取消之后我才能发出一个新的请求。 相反,我创build了一个简单的布尔包装器,这个包装器可以与一个未决的请求相关联:

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

并使用pleaseAbandonYourEfforts一个实例。 在我执行dispatch_async之前(即在上面的procedurallyBuildEnormousSpaceship BuildEnormousSpaceship中),我取消旧的请求并准备新的请求,如下所示:

 // 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; 

我的块执行asynchronous任务将不得不检查cancelThisRequest.value当然。