簡體   English   中英

你可以在GCD / dispatch_async中使用cancel / isCancelled嗎?

[英]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”中。 你必須在“wrapUpH​​ere”中釋放它們。 (如果你在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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM