你可以在GCD / dispatch_async中使用cancel / isCancelled吗? [英] Can you use cancel/isCancelled with GCD/dispatch_async?

查看:128
本文介绍了你可以在GCD / dispatch_async中使用cancel / isCancelled吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在想,你能用你用GCD推出的线程使用cancel / cancelAllOperations / .isCancelled吗?

I've been wondering, can you use cancel/cancelAllOperations/.isCancelled with a thread you have launched with GCD?

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

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

假设您想在后台进行大量处理,同时保持UI响应,以便您可以取消取消按钮(或动画显示处理器工作的动画)。我们是这样做的...

Let's say you want to do a lot of processing in the background, while keeping the UI responsive so that you can catch a cancel button (or animate something to show 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'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只会检查标志是否为真...... / p>

and CHECKER does nothing more than check it the flag is true...

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

这一切都很完美。

但........ 是否可以使用取消/取消所有操作/ .isCancelled这种类型的GCD使用?

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

这里的故事是什么?干杯。

What's the story here? Cheers.

PS - 适用于任何使用此六部分背景模板的初学者。

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

请注意,正如下面的BJ要点,每当你突破bg过程......

Note that as BJ highlights below, whenever you break out of the bg process...

在我的习语中,你必须分配所有变量,上下文,内存等,特别是在setUpHere中。你必须在wrapUpH​​ere中释放它们。 (如果你在BG中越来越深入,这个成语继续有用。)

In my idiom, you must allocate all variable, contexts, memory, etc, specifically in "setUpHere". And you must release them in "wrapUpHere". (This idiom continues to work if you go deeper and deeper, while in the BG.)

或者,正如BJ在他的例子中所做的那样。 (如果你使用BJ的方法,如果你更深入则要小心。)

Alternately, do exactly what BJ shows in his example. (If you use BJ's method, be careful if you go deeper.)

无论你使用什么方法,你必须清理你打开的任何变量/上下文/内存,你打破了BG过程。希望它有时可以帮助某人!

Whatever method you use, you must clean up any variables/contexts/memory you have open, when you break out of the BG process. Hope it helps someone, sometime!

推荐答案

GCD没有内置的取消支持;如果能够取消您的后台任务很重要,那么检查您已经证明的标志是一种可接受的解决方案。但是,您可能想要评估取消需要响应的速度;如果其中一些方法调用相当短,你可以不那么频繁地检查。

GCD does not have built-in support for cancellation; if it's important to be able to cancel your background task, then checking a flag like you've demonstrated is an acceptable solution. However, you may want to evaluate how quickly the cancellation needs to respond; if some of those methods calls are fairly short, you may be able to get away with checking less frequently.

你问是否可以使用NSOperation标志来支持取消。答案是不。 GCD不是基于NSOperation。事实上,在Snow Leopard中,NSOperation和NSOperationQueue被重新实施以在内部使用GCD。所以依赖是另一种方式。 NSOperation是一个比GCD更高层次的构造。但即使您使用NSOperation,您的取消实施也会大致相同;你还需要定期检查 self.isCancelled ,看看你是否应该放弃太空船的建造。

You asked whether you could use the NSOperation flags to support cancellation. The answer is no. GCD is not based on NSOperation. In fact, in Snow Leopard NSOperation and NSOperationQueue were re-implemented to use GCD internally. So the dependency is the other way around. NSOperation is a higher level construct than GCD. Even if you were to use NSOperation, though, your implementation of cancellation would be largely the same; you'd still have to check self.isCancelled periodically to see whether you should abandon the space ship construction.

我对你实现 CHECKER 宏的唯一担心是它实现了一个意外的返回。因此,您必须小心内存泄漏。如果你在后台线程上设置了自己的NSAutoreleasePool,那么在返回之前你需要 drain 。如果您 alloc ed或保留 ed任何对象,您可能需要 release 返回前它们。

The only concern I have with your implementation of the CHECKER macro is that it implements an unexpected return. As such, you have to be careful about memory leaks. If you've set up your own NSAutoreleasePool on the background thread, you need to drain it before returning. If you've alloced or retained any objects, you may need to release them before returning.

由于所有清理都需要在每次检查时发生,您可能需要考虑转向单一返回点。一种方法是将每个方法调用包装在 if(pleaseAbandonYourEfforts == NO){} 块中。这可以让您在请求取消后快速进入方法的最后,并将清理保存在一个位置。另一种选择,虽然有些人可能不喜欢它,但是要让宏使用调用 goto cleanup; 并定义清理:标签附近的方法,您可以释放任何需要释放的内容。有些人不喜欢以几乎宗教的方式使用 goto ,但我发现向这样的清理标签向前跳转通常比替代方案更清晰。如果您不喜欢它,将所有内容包装在 if 块中同样适用。

Since all that cleanup needs to happen at every check, you may want to consider moving toward a single return point. One way to do this would be to wrap each of your method calls in a if (pleaseAbandonYourEfforts == NO) { } block. This would let you quickly fall through to the end of the method once cancellation was requested, and keep your cleanup in a single location. Another option, though some may dislike it, would be to make the macro use call goto cleanup; and define a cleanup: label near the end of the method where you release anything that needs to be released. Some people dislike using goto in an almost religious way, but I've found that a forward jump to a cleanup label like this is often a cleaner solution than the alternatives. If you don't like it, wrapping everything in an if block works just as well.

编辑

我觉得有必要进一步澄清我之前关于单一回报点的陈述。使用上面定义的 CHECKER 宏, -buildGuts 方法可以在使用该宏的任何位置返回。如果该方法存在任何本地保留对象,则必须在返回之前清除它们。例如,想象一下对 -buildGuts 方法的这种非常合理的修改:

I feel the need to further clarify my earlier statement about having a single return point. With the CHECKER macro as defined above, the -buildGuts method can return at any point where that macro is used. If there are any retained objects local to that method, they must be cleaned up before returning. For example, imagine this very reasonable modification to 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;
}

请注意,在这种情况下,如果 CHECKER 宏导致我们在方法结束前返回,然后 formatter 中的对象将不会被释放,将被泄露。虽然 [自动快速包裹] 调用可以处理通过实例变量或全局指针可到达的任何对象的清理,但它不能释放仅可用的对象本地在 buildGuts 方法中。这就是我建议 goto cleanup 实现的原因,它看起来像这样:

Note that in this case, if the CHECKER macro causes us to return before the end of the method, then the object in formatter won't be released and will be leaked. While the [self quickly wrap up in a bow] call can handle cleanup for any objects reachable through an instance variable or through a global pointer, it cannot release objects that were only available locally in the buildGuts method. This is why I suggested the goto cleanup implementation, which 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;
}

在此实现中, formatter 总是会被释放,无论何时取消。

Under this implementation, formatter will always be released, regardless of when cancellation happens.

简而言之,无论何时你有一个宏可以让你从一个方法返回,你需要是非常肯定在你过早返回之前,所有的内存管理都已经得到了解决。使用导致返回的宏很难干净利落。

In short, whenever you mave a macro that can cause you to return from a method, you need to be very sure that before you prematurely return, all memory management has been taken care of. It's hard to do that cleanly with a macro that causes a return.

这篇关于你可以在GCD / dispatch_async中使用cancel / isCancelled吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆