Archive for August, 2011

Grand TableView Dispatch

Friday, August 19th, 2011

Just a little programming trick that might be interesting for other programmers:

I am working on a project where I have a (view based) NSTableView that should display a value in each row. These values areĀ calculated from other values that are fetched from a database. While the performance of getting one single value is acceptably fast (under 0.1 seconds) for other use cases of the application, this sums up in the table view and noticably blocks the user interface whenever the table needs to be reloaded or when scrolling through the table.

My first idea was to calculate the values in a separate thread and then update the GUI whenever a value is available. Problem here is that the model is not thread-safe at this point and that would be a ton of work to change.

So I decided that the values for the table should be calculated on the main thread as before, but the main runloop should get a chance to do its work between every calculation of a single value to keep the user interface as responsive as possible. A great situation for Grand Central Dispatch and the use of Blocks! So I wrote something like that for every requested value in the table view:

dispatch_async(dispatch_get_main_queue(), ^{
+++// calculate value and update the GUI

… unfortunately, that did not work as expected. The GUI still blocked until all values for all requested rows of the tableview where calculated. It seems the blocks were correctly added to the main queue, but as soon as the process returned to the runloop, the whole queue was processed in one pass without checking for events in between the individual enqueued blocks. So the behaviour did not change in any way. After some thinking I came up with the following interesting solution:

dispatch_async(mySerialQueue, ^{
+++dispatch_sync(dispatch_get_main_queue(), ^{
++++++// calculate value and update the GUI

So for every requested table row I add a new block to a queue that runs on a separate thread and processes its blocks in serial order. These blocks, processed on a separate thread, do just one thing: they add another block to the main queue and wait until this block is processed (dispatch_sync) before adding the next block. Finally these blocks, processed on the main thread, calculate the row values and update the GUI like before. Because of this transitions from the separate thread to the main thread, the main runloop gets a chance to process other events between every value calculation.

I find it interesting how much complicated work can be expressed in so few lines of code. Writing all this functionality “by hand” without using Grand Central Dispatch and Blocks would make it very complicated and probably less efficient.