2012-12-13

Working with wxPython in a separate thread

Sometimes you just don't want the GUI to run in the main thread. Actually, I've migrated to using wxPython in its own thread for almost all of my projects. The advantages are obvious: you can make the entire GUI thing optional, your GUI never freezes, etc. I'm not going to describe how to achieve that, just google for "wxpython long running tasks". What I'm going to write about today is one of the downsides it brings and how to overcome this little complication.


So, you've created new thread for the GUI which instantiates all the stuff and calls the application's MainLoop. The main loop takes care of all the event processing and fires up event handlers in its own context (which is different from the context of the GUI thread itself as far as I know - google for "wxpython sandwich").

Now here's the problem: imagine you want to call a GUI routine from your main, non-GUI, thread. You simply can't do it directly because the GUI's context is completely different and there's no such thing as interrupt. The first thing that probably comes to your mind is: what if I try it? Well, go ahead...


Unfortunately, this either does nothing and freezes the entire python or crashes with some C++ error (of the underlying wxWidgets). I don't remember and I'm actually too lazy to try this broken thing again.

Many unsuccessful attempts later, I've found the solution.


Now, the explanation. The do_something is the function which is supposed to be run in the context of wx. You should be able to do pretty much anything here as it should behave just like and event handler. The magic function here is wx.CallAfter which executes a function (with its arguments) asynchronously in the context of wx. The word "asynchronously" is important here because, unless I'm mistaken, it just "pickles" the function reference and arguments to a wx event and inserts it into the event queue which in turn gets processed by the MainLoop. This also means implies two things. First, wx.CallAfter returns immediately, much probably even before the execution of the function's body begins. Second, there is no direct way to get our function's return value. And that's where the wx_call decorator comes into play. I've decided to use a queue as a thread-safe blocking mechanism to wait for the function to return but there are probably other methods. Also, I had to create the return_to_queue wrapper so the original function can just return and doesn't have to know anything about the queue and the blocking at all.

If you, for any reason, need to call the function from the context of wx, it should work but I haven't tried that, yet. Sorry, no time for experiments right now, I just wanted to present to you the solution that works for my case. Feel free to comment on the topic should you have any problems and/or improvements.

Oh, and one more word of warning. I've only tried this on Windows (python 2.7, wxPython 2.8) so you mileage may vary if used on other platforms. I may update this post when I start porting the code...

If this helped you in any way, please consider donation using Flattr, PayPal or any other methods...

2 comments:

  1. First, wx.CallAfter doesn't do any pickling, it just saves a reference to the callable, the *args and **kwargs in the event object and posts the event.

    Secondly, readers may also want to take a look at wxAnyThread for a similar decorator implementation. http://pypi.python.org/pypi/wxAnyThread/

    Finally, if you would like to be able to catch the return value of the called function without blocking the calling thread, then the wx.lib.delayedresult module is worth a look.

    Robin

    ReplyDelete
    Replies
    1. thanks for the hints, robin! i will investigate all of them and will probably refactor/improve my solution!

      Delete