views:

509

answers:

1

I'm just playing around with GCD and I've written a toy CoinFlipper app.

Here's the method that flips the coins:

- (void)flipCoins:(NSUInteger)nFlips{

    // Create the queues for work
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);

    // Split the number of flips into whole chunks of kChunkSize and the remainder.
    NSUInteger numberOfWholeChunks = nFlips / kChunkSize;
    NSUInteger numberOfRemainingFlips = nFlips - numberOfWholeChunks * kChunkSize;

    if (numberOfWholeChunks > 0) {
        for (NSUInteger index = 0; index < numberOfWholeChunks; index++) {
            dispatch_async(queue, ^{
                NSUInteger h = 0;
                NSUInteger t = 0;
                flipTheCoins(kChunkSize, &h, &t);
                dispatch_async(mainQueue, ^{
                    self.nHeads += h;
                    self.nTails += t;
                });
            });
        }
    }
    if (numberOfRemainingFlips > 0) {
        dispatch_async(queue, ^{
            NSUInteger h = 0;
            NSUInteger t = 0;
            flipTheCoins(numberOfRemainingFlips, &h, &t);
            dispatch_async(mainQueue, ^{
                self.nHeads += h;
                self.nTails += t;
            });
        });

    }
}

As you can see; I'm breaking the number of flips into large chunks flipping them in the background and updating properties in the main queue. The properties are being observed by the window controller and an UI is updated with the running results.

I've looked through the Concurrency Programming Guide and the GCD docs, and although there is a way to suspend a queue, there isn't a way to stop them, and remove all queued and not running objects.

I'd like to be able to hook up a 'stop' button to cancel flipping once it's started. With NSOperationQueue I can observe the operationCount property to know if it's running, and cancelAllOperations to remove queued blocks.

I've looked through the Concurrency Programming Guide and the GCD docs, and although there is a way to suspend a queue, there isn't a way to stop them, and remove all queued and not running objects.

So :-

  1. How do I tell if blocks I've added to a queue are still waiting?
  2. How do I cancel blocks that haven't run yet?
  3. I'm new to the GCD stuff, so am I doing it right?
+4  A: 

This is a semi-common question when programming with GCD.

The short answer is that GCD does not have a cancelation API for queues. The rational:

  1. memory management would become vastly more complicated, because a given block might be responsible for free()ing a given allocation of memory. By always running the block, GCD ensures that memory management remains easy.
  2. It is practically impossible to halt a running block without corrupting state.
  3. Most code that needs cancellation logic is already tracking that state in private data structures.

Given all of these cases, it is far more efficient and powerful to write code like this:

dispatch_async(my_obj->queue, ^{
    bool done = false;
    // do_full_update() takes too long, therefore:
    while ( !my_obj->cancelled && !done ) {
        done = do_partial_update(my_obj);
    }
});

Oh, and to know if a queue has finished running all of the enqueued blocks, your code can simply execute an empty block with the synchronous API:

dispatch_sync(my_obj->queue, ^{});

Good luck and have fun!

Anonymous
Or instead of dispatching sychronously, you could create a new dispatch_group, dispatch all your blocks asynchronously to the same group, and then wait for the group to finish. I think this adds maybe 3 lines to the total line count (1 to create the group, 1 to wait on it, and 1 to destroy the group).
Dave DeLong