All posts

Feeding the Main Queue with Grand Central Dispatch

Originally posted on Tumblr, 6 September 2012.

So here’s the thing: you want to be a good citizen, and dispatch your expensive operations to a background queue, but it is impossible or difficult to do so because your code uses frameworks that must be accessed from the main thread. You could go to the effort of copying data into dictionaries and arrays, and passing those around, in order to avoid accessing the framework in the background, but it is a major headache.

Instead, you think, I could slice the problem up into small chunks of work, and run them on the main queue. That shouldn’t interrupt the user interface too much, which is why you need to avoid the main queue in the first place.

So you submit a few hundred blocks of work to the main queue, each of which does a very short calculation and completes. But to your horror, when you run the app, it beach balls regardless. The blocks you are submitting get queued up one after another, but then event handling blocks have to queue up and wait for all of your blocks to complete. Back to square one.

What you really need to do is feed one block to the main queue, and only feed the next one when the first is finished. That way, event handling can interleave with your work blocks. But how do you do that?

I’ve wrestled with this scenario a few times over the years, but I think I have finally found a good solution, and one which leaves the user interface flowing like soft butter. Not only that, it’s really straightforward.

The idea is to create a background serial queue, and enqueue blocks that submit your original blocks synchronously to the main queue.

-(void)enqueueBlock:(void (^)(void))block
{
    static dispatch_queue_t feederQueue = NULL;
    if ( !feederQueue ) feederQueue = 
        dispatch_queue_create("com.mentalfaculty.feederqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(feederQueue, ^{
        dispatch_sync(dispatch_get_main_queue(), block);
    });
}

Then, you enqueue work like this

[self enqueueBlock:^{
    // Do some work on the main thread
    // Not too much!
    ...
}];

It’s simple, and best of all, it works a treat.

The next logical step would be a simple feeder class that can perform this sort of task for any queue you choose.