I have a two-thread application: GUI, and some background work. I'm trying to send requests to the main thread to do GUI updates (move a progress bar), but it doesn't seem to work. I've boiled it down to a really minimal example:
import pygtk
pygtk.require('2.0')
import glib
import gtk
import threading
import sys
import time
def idle():
sys.stderr.write('Hello from another world.\n')
sys.stderr.flush()
gtk.main_quit()
def another_thread():
time.sleep(1)
glib.idle_add(idle)
thread = threading.Thread(target=another_thread)
thread.start()
gtk.main()
This should, I thought, print something to standard error from the main/GUI thread, but nothing happens. And it doesn't quit, either, so gtk.main_quit isn't being called.
Also, adding more output to stderr acts weirdly. If I change the thread's function to:
sys.stderr.write('----\n')
sys.stderr.write('----\n')
sys.stderr.flush()
sys.stderr.write('After.\n')
sys.stderr.flush()
I see 1, sometimes 2 lines out output. It looks like some kind of race condition with the main thread entering gtk.main, but I don't know why this would be.
You need to init glib's thread support before using glib in a multi-threaded environment. Just call:
glib.threads_init()
Before calling into glib functions.
Why not use glib.timeout_add_seconds(1, idle) and return False from idle() instead of starting a thread and then sleeping 1 second? Starting an idle function from another thread is quite redundant, since idle functions already run in another thread.
EDIT:
By "starting an idle function from another thread is redundant", I meant that you don't have to start an idle function in order to mess with the GUI and update the progress bar. It is a myth that you can't mess with the GUI in other threads.
Let me restate here what is needed to do GTK calls from other threads:
glib.threads_init() or gobject.threads_init(), whichever you have, as discussed in vanza's answer.gtk.gdk.threads_init(). I am pretty sure you are right in your answer, this only has to be called before gtk.main(). The C docs suggest calling it before gtk_init(), but that's called at import time in PyGTK if I'm not mistaken.gtk.main() between gtk.gdk.threads_enter() and gtk.gdk.threads_leave().Bracket any calls to GTK functions from:
between gtk.gdk.threads_enter() and gtk.gdk.threads_leave().
Note that instead of surrounding your calls with enter/leave pairs, you can also use with gtk.gdk.lock: and do your calls within that with-block.
Here are some resources which also explain the matter:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With