Mar 12, 2007

Threading and (py)Gtk

At the request of a few people I have made a demo to share, showing how I do threading in a pygtk application (Conduit). I find the following approach seems to work reliably and requires little code. Other approaches can be found here and here.

This approach takes advantage of the fact that signal emission in glib has been threadsafe since glib 2.8 (IIRC). All communication with the GUI is done via gobject signals. There is definitely a compromise in the level of GUI fiddling that you can do, particularly when compared with the threads_enter/leave approach. However, I have found this signal based approach sufficent in my case, and that it encourages me to decouple the slow blocking tasks from the GUI.

A lot of the tasks in conduit take a long time (network limited), and there is no real need for extensive GUI interaction with them once they have been started. I am really only interested in their progress, and when they complete. With this in mind I have implemented the following approach;

  • FooThreadManager This class is the entry point for starting threads (the make_thread() method) . Its basically just a threadpool that starts threads with the appropriate arguments, while restricting the number of concurrent running threads below a user defined limit. It also connects the threads to the supplied user callbacks.

  • _IdleObject Like a normal gobject.GObject but emits all signals in the main thread

  • __FooThread_ A simple class which derives from both threading.Thread and _IdleObject. All work is done in run() and a signals are emitted when the thread completes and to show progress

  • Demo A simple demo (see screenshot) which can start a whole bunch of threads and receive notification when they complete.

FooThread Test Program

Anyway, the Example Code is a bit contrived and will certainly need some customization by the user but nonetheless may still be useful to others.

Update: Thanks to comments I fixed up a thread-safety issue. I had misunderstood that signal handlers get run immediately in emit(). Now the code emit()s on an idle_handler so all signals and callbacks are run in the main thread. As i mentioned, I use this approach in situations where the threads may run and block for a long time, so the burden of processing all the signals in the main thread is not a big deal as they do not occur frequently.

Update 2: Added progress reporting