I recently put together a little utility application to bundle some repetitive tasks within a project I’ve been working on into a UI with a console output (namely push to git and then deploy with capistrano).

Because I’m still not entirely comfortable with Objective-C, and I’m still reading up on the Cocoa framework I decided to build my app using MacRuby so I could blend what I’m good at with some new things as well, and a shiny interface.
The whole process was actually really good – XCode is set up with the MacRuby application templates when you install the latest build, and setting up IBOutlets and IBActions is as simple as using attr_accessor and
utlet_namedef action_name(sender) – how much simpler can you get?
So things were running pretty smoothly with my test commands, everything was returning quickly, the console output was being appended as expected… until I switched my commands to something which was a bit more longer running –
git push
. Why is this an issue? It all comes down to Blocking, and UI Responsiveness….
Unfortunately the backtick, popen and popen3 methods all hold onto the executing thread until they are done, and if this is in your main thread (the UI Thread), which would normally be the case if you are responding to a button click event, you going to see the ‘spinning beach ball of doom!’.
Fortunately in the latest release of MacRuby (currently 0.5 beta) support for Grand Central Dispatch (note: you’ll need Snow Leopard for this…) is included and makes the whole threading, synchronisation, locks etc as simple as a few lines of code… So how is it done?
DISPATCH_QUEUE = Dispatch::Queue.new("net.amasses.app_name.queue_name")
DISPATCH_QUEUE.dispatch do
IO.popen(full_cmd) do |streamer|
while line = streamer.gets
Dispatch::Queue.main.dispatch { console_output.insertText line }
end
end
Dispatch::Queue.main.dispatch do
console_output.insertText "\r\r Done \r\r"
go_button.setEnabled true
end
end
So, a few small points.
1. The DISPATCH_QUEUE.dispatch do block basically tells the runtime to execute anything within this block on the new queue thread. Yes, it is that simple.
2. If you want to do things with your UI you will need to switch back to the main (or UI) queue – this is done with Dispatch::Queue.main and it acts just like a regular dispatch queue.
Keep in mind that your means might vary, and I haven’t even dipped my toes into locks and synchronization with Semaphores, but from what I have seen (and how I understand it) it isn’t much harder then what you see here.
Enjoy!
MacRuby header image from http://blog.anthonyburns.co.uk/
This can be even shorter! There is no need to create your own queue in this example, since you’re just firing something async into the background. You can just as easily skip the queue creation and do:
Dispatch::Queue.concurrent.async do … end around the IO.popen block!
Thanks for the pointer towards the concurrent.async blocks.
I glanced over that in the apple GCD docs and didn’t bother digging into this further, but I’ll have to keep that in mind in the future