问题
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.
回答1:
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.
回答2:
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:
- Call
glib.threads_init()orgobject.threads_init(), whichever you have, as discussed in vanza's answer. - Call
gtk.gdk.threads_init(). I am pretty sure you are right in your answer, this only has to be called beforegtk.main(). The C docs suggest calling it beforegtk_init(), but that's called at import time in PyGTK if I'm not mistaken. - Bracket your call to
gtk.main()betweengtk.gdk.threads_enter()andgtk.gdk.threads_leave(). Bracket any calls to GTK functions from:
- threads other than the main thread
- idle functions
- timeouts
- basically any callback other than signal handlers
between
gtk.gdk.threads_enter()andgtk.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:
- http://www.yolinux.com/TUTORIALS/GDK_Threads.html
- http://blogs.operationaldynamics.com/andrew/software/gnome-desktop/gtk-thread-awareness.html
回答3:
The following makes the above work for me:
A call to gtk.gdk.threads_init() before doing anything else: before starting threads, before entering gtk.main(). I think in reality, this only needs to be called before entering gtk.main(), but it is easy enough to call it while everything is single threaded and simple.
In the idle callback (idle, in the example), a call to gtk.gdk.threads_enter() before GTK stuff (easy to do just at the top of the function), and a call to gtk.gdk.threads_leave(), before the end of the function.
gtk.gdk.threads_init() seems to also tell PyGTK not hold the GIL when it goes to sleep - I think I was missing some output from the aux. thread in the example just because the sleeping main thread (sleeping in gtk.main()) was still holding the GIL. gtk.gdk.threads_init(), as far as I can tell, instills good mojo into PyGTK and GTK+. Because of this, gtk.gdk.threads_init() is needed even if you launch a thread that doesn't touch GTK+, doesn't do anything with glib, gobject, etc., just does basic computation.
来源:https://stackoverflow.com/questions/3579221/gtk-idle-add-not-running