Grand Central Dispatch

If you’re looking for information on how Apple is trying to solve the concurrency problem for programmers (on the Mac anyway) check out pages 11 through 13 of John Siracusa’s review of Snow Leopard.

In a nutshell, they added a feature called blocks to C (and Objective C and soon C++) which are basically what other languages call closures, and they added a task queueing system and dispatch system that creates threads as required and balances jobs (from multiple applications) among them.

What’s really elegant is how these two things, blocks and dispatch, come together to make taking existing code and changing it to support threading, easy.

- (IBAction)analyzeDocument:(NSButton *)sender 
{ 
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSDictionary *stats = [myDoc analyze]; 
    dispatch_async(dispatch_get_main_queue(), ^{
      [myModel setDict:stats]; 
      [myStatsView setNeedsDisplay:YES]; 
      [stats release];
    });
  });
}

Or, in C++ speak:

void CMyDoc::OnAnalyze()
{ 
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
    MyStats *stats = Analyze();
    dispatch_async(dispatch_get_main_queue(), ^{
      SetStats(stats);
      GetStatsView()->ShowWindow(SW_SHOW);
      delete stats;
    });
  });
}

This code runs an Analyze function in the background, and then gives the results of the analyze back to the main thread to display. The key to keeping the changes all local to the function are the code blocks introduced using the ^ operator, which lets you pass a code block as an argument.

This is the most pragmatic method I’ve seen to moving background processing off the main thread. There’s still some work to make sure that the user doesn’t pick the Analyze function while there’s already an Analyze running.

You’d want to centralize that – having an application-level list of background jobs that are currently processing, so you can display a “Processing…” indication somewhere on the UI and disable buttons that would kick off duplicate jobs. Even after adding that, the function is still manageable:

void CMyDoc::OnAnalyze()
{ 
  // Presumably your OnUpdateAnalyzeUI would do this and cause the 
  // Analyze menu item and toolbar button to disable, but let's do it here 
  // to demonstrate
  if (JobQueue::GetInstance().IsExecuting(IDC_ANALYZE))
    return;

  JobQueue::GetInstance().NotifyStartJob(IDC_ANALYZE);

  dispatch_async(dispatch_get_global_queue(0, 0), ^{
    MyStats *stats = Analyze();
    dispatch_async(dispatch_get_main_queue(), ^{
      SetStats(stats);
      GetStatsView()->ShowWindow(SW_SHOW);
      delete stats;

      JobQueue::GetInstance().NotifyEndJob(IDC_ANALYZE);
    });
  });
}

Still a pretty small price to pay.