[英]Can you use cancel/isCancelled with GCD/dispatch_async?
我一直在想,你能用你用GCD推出的線程取消/ cancelAllOperations / .isCancelled嗎?
目前,我只使用布爾值作為標志來取消后台進程。
假設您希望在后台進行大量處理,同時保持UI響應,以便您可以捕獲取消按鈕(或動畫顯示處理器工作的動畫)。 這是我們如何做到的......
@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;}
一切都很完美。
但........ 是否可以使用取消/取消AllOperations / .isCancelled與這種類型的GCD使用?
這是什么故事? 干杯。
PS - 適用於使用此“六部分”背景模板的任何初學者。
請注意,正如下面BJ所述,每當你突破bg進程時...
在我的習語中,你必須分配所有變量,上下文,內存等,特別是在“setUpHere”中。 你必須在“wrapUpHere”中釋放它們。 (如果你在BG中越走越深,這個成語繼續有效。)
或者,正如BJ在他的例子中所展示的那樣。 (如果你使用BJ的方法,如果你更深入,要小心。)
無論使用何種方法,當您退出BG流程時,必須清理已打開的任何變量/上下文/內存。 希望有時幫助某人!
GCD沒有內置的取消支持; 如果能夠取消你的后台任務很重要,那么檢查你所展示的標志是一個可以接受的解決方案。 但是,您可能想要評估取消需要響應的速度; 如果這些方法中的一些調用相當短,那么您可以更頻繁地檢查。
您詢問是否可以使用NSOperation標志來支持取消。 答案是不。 GCD不是基於NSOperation。 事實上,在Snow Leopard中,NSOperation和NSOperationQueue被重新實施以在內部使用GCD。 所以依賴是另一種方式。 NSOperation是一個比GCD更高層次的構造。 但是,即使你要使用NSOperation,你的取消實現也會大致相同; 你還需要定期檢查self.isCancelled
,看你是否應該放棄太空船的建造。
我對CHECKER
宏的實現唯一關注的是它實現了意外的return
。 因此,您必須小心內存泄漏。 如果您在后台線程上設置了自己的NSAutoreleasePool,則需要在返回之前將其drain
。 如果您已alloc
ed或retain
ed任何對象,則可能需要在返回之前release
它們。
由於所有清理工作都需要在每次檢查時進行,因此您可能需要考慮轉向單一返回點。 一種方法是將每個方法調用包裝在if (pleaseAbandonYourEfforts == NO) { }
塊中。 這可以讓您在請求取消后快速進入方法的最后,並將清理保存在一個位置。 另一種選擇,雖然有些人可能不喜歡它,但是會讓宏使用調用goto cleanup;
並在方法結尾附近定義一個cleanup:
標簽,您可以在其中釋放需要釋放的任何內容。 有些人不喜歡以幾乎宗教的方式使用goto
,但我發現向這樣的清理標簽向前跳轉通常比替代方案更清晰。 如果您不喜歡它,將if
所有內容包裝起來也同樣適用。
編輯
我覺得有必要進一步澄清我之前關於單一回歸點的陳述。 使用上面定義的CHECKER
宏, -buildGuts
方法可以在使用該宏的任何位置返回。 如果該方法存在任何本地保留對象,則必須在返回之前清除它們。 例如,想象一下對-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;
}
請注意,在這種情況下,如果CHECKER
宏導致我們在方法結束之前返回,則formatter
的對象將不會被釋放並將被泄露 。 雖然[self quickly wrap up in a bow]
調用可以處理通過實例變量或全局指針可到達的任何對象的清理,但它不能釋放僅在buildGuts
方法中本地可用的對象。 這就是為什么我建議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
。
簡而言之,每當你制作一個可能導致你從方法返回的宏時,你需要非常確定在你過早返回之前,所有的內存管理都已經得到了解決。 使用導致返回的宏來干凈利落是很難的。
謝謝你的討論! 在我的情況下,我想允許發出一個新的異步請求,如果尚未完成,將取消之前的異步請求。 通過上面的例子,我必須以某種方式通過attentionBGIsAllDone
回調等待信號,在我發出新請求之前,未完成的請求被取消。 相反,我創建了一個簡單的布爾包裝器,我可以將其與一個未完成的請求關聯起來:
@interface MyMutableBool : NSObject {
BOOL value;
}
@property BOOL value;
@end
@implementation MyMutableBool
@synthesize value;
@end
並為pleaseAbandonYourEfforts
使用它的一個實例。 在我執行dispatch_async
之前(即在上面的procedurallyBuildEnormousSpaceship
),我取消舊請求並准備新請求,如下所示:
// 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;
我執行異步任務的塊當然必須檢查cancelThisRequest.value
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.