Matt Rajca

blog projects github twitter email

Canceling Blocks in GCD

April 23, 2016

In OS X "Yosemite" and iOS 8, Apple introduced the ability to cancel blocks dispatched onto GCD queues. Previously, one had to use NSOperations or rely on hacks to achieve this. Unfortunately, many people don't realize this functionality now exists in GCD, and using it is quite easy.

Suppose we're scheduling a block for delayed execution with dispatch_after:

    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"Hello.");
    });
    

To cancel the block before it gets a chance to execute, we first have to obtain a reference to it. We can do this by creating a wrapper object of type dispatch_block.

    
    dispatch_block_t work = dispatch_block_create(0, ^{
        NSLog(@"Hello.");
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), work);
    

Now if we place a call to dispatch_block_cancel immediately after the call to dispatch_after, you'll notice the block never executes:

    
    dispatch_block_cancel(work);
    

One thing to note is that dispatch_block_cancel is not pre-emptive. If the worker block is in the middle of a long-running operation, dispatch_block_cancel won't force-terminate it. To do this, we have to periodically test for cancellation with dispatch_block_testcancel. Here's an example:

    
    for (...) {
        /* do some work */
        [NSThread sleepForTimeInterval:0.2];

        if (dispatch_block_testcancel(work) != 0) {
            /* exit gracefully */
            return;
        }
    }
    

And that's all it takes to cancel blocks in GCD!