问题
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