A Simple Job Queue With Grand Central Dispatch

by Ed on June 27, 2010

In my last two posts, we talked about the basics of GCD and then delved into blocks. Now we’re going to start learning how to replace your old threading code with GCD. For this post, we’ll discuss how to create a simple sequential job queue using dispatch_async and friends.

Starting Up…

While we’ve seen that we can easily put things onto a concurrent queue, in many cases, you’ll want to create your own FIFO queue. These allow you to:

  1. guarantee order of operations
  2. implement synchronization without locks
  3. arrange these queues into groups to help wait on multiple queues at once
  4. know when a queue is empty

The synchronization case will be left for my next post, but we’ll try to cover the others here.

First, we need to create a queue. Simple enough:

dispatch_queue_t myQueue = dispatch_queue_create("com.mycompany.myqueue", 0);

The first parameter is just some identifier, you can use anything you want, including NULL, but reverse domain is usually a good case. You’ll actually see this name in backtraces in gdb to help you know which queue is which.

Once you have a queue, it’s just like we’ve seen before. We can start to put tasks on it.

dispatch_async(myQueue, ^{
    printf("this is a block!\n");
});

And as noted, whenever you create your own queue, it is a FIFO queue. So everything will execute in the order you put blocks onto the queue. And you always know that the preceding block is complete before the next block executes.

Finished Yet?

We’ve implemented a simple job queue and can put blocks onto it, but how do you know when it is done? There are two ways you can determine this. First, you can just use dispatch_sync to help you.

// wait for queue to empty
dispatch_sync(queue, ^{});

Basically, we’re using dispatch_sync here to force us to wait until the block passed finishes before continuing. Your thread will block until the block finally gets a chance to run and finish. At that point, you know the queue is empty (assuming you’re not doing something like posting to that queue from other queues).

You should generally only use the method above if you only have one queue and don’t mind blocking your thread until the task is done. For tasks that take a while, that would be bad, particularly on iOS where you’ll get watchdogged if your main thread is blocked for too long.

If you don’t want to wait, then you could simply push an async block to notify you when the block runs. You might, for example, do something like this during shut-down of your object (and related queue).

- (void)shutdown {
    // don't allow more blocks to be queued
    _valid = NO;

    // allow our owner to release us while we clean up
    [self retain];

    // now push an async task to tell us when we're done
    dispatch_async(_queue, ^{
        [self allTasksDone];
    }
}

- (void)allTasksDone {
    // we're really done and ready to shut down
    // we can now release ourselves
    [self release];
}

Dispatch Groups

The other method to do this involves dispatch groups. This allows you to create and wait on multiple queues to become empty, though it would also work for one queue. To create a group is even simpler than creating a queue. Once you have your group, you submit blocks to the group, as such:

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
    printf("This block is associated with our group\n");
});

Note that we used dispatch_group_async instead of dispatch_async. This ensures the block is associated with the group. To wait on the group, we just call:

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

The second parameter just indicates how long to wait before giving up. Most times you’d pass DISPATCH_TIME_FOREVER as we did here.

Keep in mind that only blocks that you started with dispatch_group_async and therefore were associated with the group can be waited on with dispatch_group_wait. If you started that block otherwise, you’d be waiting forever, indeed.

Like we talked about above, this is going to block the thread you call this on though. How would we instead notify when all queues were empty? There’s an API for that:

dispatch_group_notify(group, queue, ^{
    [self allTasksDone];
});

This will call allTasksDone when all blocks submitted to the group are finished, regardless of how many queues comprise the group.

Canceling Tasks

Often times, you want to put tasks onto a job queue, and then cancel one or more of them. However, you can’t cancel a block via any sort of API, so they always must run to completion. So it’s up to us to expedite that if need be.

In the example above, we wanted to wait until all tasks were done. Before submitting our sentinel block, we set _valid to NO. We can use this to help us do cancelation and hence shut the queue time in a timely manner. Perhaps you’re writing image thumbnails and you want the current one to finish, but all others to abort. You can just check the _valid flag in your blocks:

dispatch_async(queue, ^{
    if (_valid) {
        [self processFile:fileURL];
    }
});

Once that flag is set every block that started to run would instead immediately exit.

Another cancelation idiom is the case where you start a task that’s searching for data on one query and the query has changed. The canonical example might be type-ahead. You can accomplish this by having a sort of ever-changing sentinel. You could do this as a integer seed, or in this case, perhaps we just store off a copy of the query we are doing and we compare that to the ‘current’ query. We’ll need some synchronization for this because the term will be referenced from our main thread and the thread the block is running on.

- (NSString *)currentTerm {
    @synchronized(self) {
        return [[_term copy] autorelease];
    }
}

- (void)setCurrentTerm:(NSString *)term {
    @synchronized(self) {
        if (term != _term) {
            [_term release];
            _term = [term copy];
        }
    }
}

- (void)searchWithTerm:(NSString *)term {
    self.currentTerm = term;

    dispatch_async(_queue, ^{
        [self performSearchWithTerm:term];
    });
}

- (void)performSearchWithTerm:(NSString *)term {
    if ([term isEqualToString:self.currentTerm]) {
        // do searching here and periodically double-check
        // term against self.currentTerm to see if it changed

        // When we have results, pass them to the main thread
        // Must check against current term again for safety.
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([term isEqualToString:self.currentTerm]) {
                [self searchReturnedResults:results];
            }
        }
    }
}

This one shows not only how to check against the current search term, but it also demonstrates another pattern where you return results.

Note that we store off the current term into the object, but we pass the original term into our block. While the block executes, we periodically compare our search term to the current term. If it changes, we stop processing and exit. However we might get as far as finding and returning results, which we then send back to the main thread. But we could have changed the current term in between the time when the block was queued for the main thread and the time that block was executed, so we check the term for one last time before returning results. It’s extremely important to do this check, otherwise you’ll end up wondering why you’re processing data that’s no longer valid, or worse.

Until Next Time

That pretty much covers creating and using a job queue. We discussed how to create a queue and add tasks, how to wait for tasks to complete, and also how to effectively cancel tasks in the queue. We also ended up covering how to successfully shut down an object that owns a queue. These patterns should help you replace your current use of threads with GCD. If only we could get rid of locking. Oh wait, we can! That’s for next time :-)

{ 13 comments… read them below or add one }

Jonathan Wight June 28, 2010 at 9:21 am

What are you trying to do that an NSOperationQueue can’t?

Reply

Ed June 28, 2010 at 9:30 am

An NSOperationQueue could do this, yes. But then you need to create operation objects, etc. to wrap all your code in. You don’t need any of that with GCD. It’s less typing, more straightforward, and far more flexible, and you can take advantage of blocks. And since NSOperationQueue is built on top of GCD, you can eliminate the middleman. NSOperationQueue would never exist if GCD had come first.

Implement it both ways and you’ll find that the complexity is far lower using GCD.

Reply

Preston January 24, 2011 at 12:09 am

“NSOperationQueue would never exist if GCD had come first.”

This isn’t necessarily true. However, there are features that the lower-level GCD has which aren’t replicated yet in NSOperationQueue, but the reverse is also true, such as the ability to specify dependencies between NSOperations as well as KVO.

The recommended way is to use the higher-level API, dropping to the lower-level only when needed. Applications that used NSOperation in Leopard were automatically using GCD in Snow Leopard…and that will also be true of whatever comes in the future. :)

Reply

Jonathan Wight June 28, 2010 at 7:08 pm

There’s NSBlockOperation already and NSOperation support completion blocks.

It’s not “less typing” if you have to write most of NSOperationQueue.

Apple Engineers recommended at WWDC using NSOperationQueue in ObjC progreams.

Reply

Ed June 28, 2010 at 9:55 pm

For my uses, the only thing that NSOperationQueue gives you is above GCD is a cancellation flag. And I don’t see any equivalent to dispatch groups (short of waitUntilAllOperationsAreFinished, which is purely synchronous), which I also use. In general, I would rather use the lower-level service unless there’s some strong benefit to the higher-level one (I wouldn’t use CFRunLoopTimers directly, for example, since NSTimer is so much easier), but in this case, I’m not seeing the win. If you are, that’s fine.

Regardless, my point with these posts is to show how to use GCD directly though.

Reply

Rob February 24, 2013 at 5:26 am

I know this is an old thread, but if I stumbled across it, others might, too, so I’ll inject an observation: This discussion undersells the wonderful features of operation queues, which offer quite a few enhancements over GCD. For example, concurrent queues can be created with maxConcurrentOperationCount, namely a concurrent queue, but limited number of concurrent operations, which is very important in background network operations; you can dependencies between operations (which gives you the sort of control you get with dispatch groups, but more refined); the ability to cancel queued operations is much more robustly implemented than the above GCD examples; etc. I use both GCD and operation queues, but I find lots of situations where the NSOperationQueue solution is much easier (and very few situations where the converse is true). There are times I use GCD, but this discussion thread (no pun intended) is giving operation queues short shrift.

Reply

Aaron Mathias November 22, 2010 at 6:38 pm

can you post directions for what framework needs to be added to my xcode project as well as what import statements i need to add to my class files.

Reply

Ed November 23, 2010 at 9:46 am

You don’t need to link to anything special. It’s build into libSystem. As for includes, just do

#import <dispatch/dispatch.h>

Reply

luke January 13, 2011 at 2:40 am

The best Blocks tutorial I have found yet.

Reply

Chris March 6, 2011 at 6:45 pm

Thanks for a good, concise tutorial. First one I’ve been able to find.

Reply

Vijay November 24, 2011 at 10:52 pm

When we are fetching some data from internet through blocks if suddenly network fails then what can we do to handle that and how can we avoid crash iphone application

Reply

Diethard December 8, 2011 at 10:14 pm

Thanks for this very good tutorial.
Can you please write soemthing about using core data and performBlock and performBlockAndWait and the new concurrency types? How to use in different async blocks to do fetches parallel wihtout blocking? What should be inside a performBlock, also the save call? When to use parentContext?

Reply

Pcaso February 29, 2012 at 7:37 am

Hi
Had a query thats boggling my mind.
Scenario:
Create a View inside UISCrollviewCon ( subview)
Run dispatch_async inside Uiview to update image inside the view.

how to handle scenarios when view is getting lazily deallocated.
Should I put all activities in a global queue ? or make the pattern as “isCancelled” and set it to be YES while deallocating? Any help would be useful.

Reply

Leave a Comment

Previous post:

Next post: