views:

79

answers:

3

Is there a way to simulate a break statement in a dispatch_apply() block?

E.g., every Cocoa API I've seen dealing with enumerating blocks has a "stop" parameter:

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger i, BOOL *stop) {
    if ([obj isNotVeryNice]) {
        *stop = YES; // No more enumerating!
    } else {
        NSLog(@"%@ at %zu", obj, i);
    }
}];

Is there something similar for GCD?

+1  A: 

I don't think dispatch_apply supports this. The best way I can think of to imitate it would be to make a __block boolean variable, and check it at the beginning of the block. If it's set, bail out quickly. You'd still have to run the block through the rest of the iterations, but it would be faster.

BJ Homer
A: 

You can't break a dispatch_apply since it's illogical.

In -enumerateObjectsUsingBlock: a break is well-defined because the functions are run sequentially. But in dispatch_apply the functions are run in parallel. That means at the i=3rd invocation of the "block", the i=4th call could have been started. If you break at i=3, should the i=4 call still run?

@BJ's answer is the closest you can do, but there will always some "spill-over".

KennyTM
Well, that's not entirely true; while it's true that enumerateObjectsUsingBlock: is sequential, there's also enumerateObjectsWithOptions:usingBlock:. That "options" parameter can be used to direct that the enumeration should happen concurrently.I'm not sure how they're doing that internally, but I'd guess it's done using a dispatch_group which would allow more direct control.
BJ Homer
But the point is that enumerateObjectsWithOptions:usingBlock: still has the *stop parameter.
BJ Homer
It would be logical to support stopping in dispatch_apply(), but it didn't make sense within the design goals. Stating that the stop flag on `enumerateObjectsUsingBlock:` exists because of implied sequential execution is wrong; the two are completely orthogonal.
bbum
+5  A: 

By design, dispatch_*() APIs have no notion of cancellation. The reason for this is because it is almost universally true that your code maintains the concept of when to stop or not and, thus, also supporting that in the dispatch_*() APIs would be redundant (and, with redundancy comes errors).

Thus, if you want to "stop early" or otherwise cancel the pending items in a dispatch queue (regardless of how they were enqueued), you do so by sharing some bit of state with the enqueued blocks that allows you to cancel.

if (is_canceled()) return;

Or:

__block BOOL keepGoing = YES;
dispatch_*(someQueue, ^{
    if (!keepGoing) return;
    if (weAreDoneNow) keepGoing = NO;
}

Note that both enumerateObjectsUsingBlock: and enumerateObjectsWithOptions:usingBlock: both support cancellation because that API is in a different role. The call to the enumeration method is synchronous even if the the actual execution of the enumerating blocks may be fully concurrent depending on options.

Thus, setting the *stopFlag=YES tells the enumeration to stop. It does not, however, guarantee that it will stop immediately in the concurrent case. The enumeration may, in fact, execute a few more already enqueued blocks before stopping.

(One might briefly think that it would be more reasonable to return BOOL to indicate whether the enumeration should continue. Doing so would have required that the enumerating block be executed synchronously, even in the concurrent case, so that the return value could be checked. This would have been vastly less efficient.)

bbum