Thread-safe event passing in wxPython
wxWidgets are my favourite widget toolkit for making quick and dirty utilitarian interfaces in Python.
The bindings are called wxPython. Why I like wxPython?
I find Tkinter too basic, Qt needing too much boilerplate code, and GTK (PyGobject)
too fiddly to set up on Windows (maybe it has since improved). While getting wxPython is as easy as
pip install wxPython
on all platforms.
Most applications run some kind of background tasks. If the whole application was single threaded the GUI would freeze until the background tasks complete so a common technique is to have separate threads for the GUI and the "backend". Multithreading naturally leads to the issue of synchronization. For example you should not destroy a button from one thread when it is being repainted by another thread.
Changing the GUI from other threads is asking for trouble. There are mechanisms to handle events when it is safe for the GUI. Even though CPython runs basically in a single thread (due to the GIL) all "foreign" libraries (like wxWidgets written in C++) can run in their own threads and use everything the OS provides. Violating thread-safety usually leads to outright crashes in the C++ libraries which are hard to diagnose from the Python end. There may not even be a Python exception raised. Just a pure crash.
My wxPython flow
I usually start an app by designing the static elements of the window in wxFormBuilder and autogenerating Python code. Then, I derive my own class from the autogenerated code to add custom things.
In almost every app GUI events have to go in two ways. From the GUI to the backend (worker threads) and from the backend to the GUI. Passing events from the GUI is easy as the callbacks can use standard Python queues (GUI puts data/events into the queue, worker thread reads the events from the queue).
The other direction (backend to GUI) is more tricky and differs between various widget toolkits.
Event container class
First step is to have a container class to represent the events:
1 2 3 4 5 6 7 8 9 10 |
|
This is basically a wrapper. The payload can be anything. I prefer passing plain dictionaries because they are self-describing and because I am not concerned with performance (I don't pass that many events). Any data type could be used. Passing raw integers or tuples is also an option.
The GUI
This is a minimal example. The important parts are:
- Registering the custom event handler (
process_backend_event
). - Processing the events in
process_backend_event
. This function runs when it is safe to modify the GUI. - The
feed_event
function that can be called from any thread. It simply passes thepayload_dict
towx.PostEvent
. This makes passing the event thread-safe. - Callbacks from GUI widgets put their own events into
outbound_event_queue
. This queue should be read by the backend thread.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|