tags:

views:

108

answers:

3

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.

+2  A: 

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.

vanza
GDK threads need to be initialized too.
ptomato
I have no idea why this got downvoted - I think it's on the right track. The only thing is, `glib` doesn't seem to have a `threads_init` function. (At least in PyGTK.) It's documented, but it's not there.
Thanatos
`glib` is part of PyGObject, not PyGtk (anymore). And on my Ubuntu 10.04 system `glib` does have a working `threads_init`.
JanC
@Jan Claeys: I can `import gobject; gobject.threads_init()`, but not the same with `glib`. pygobject 2.20.0 and pygtk 2.16.0, Gentoo.
Thanatos
Looks like they changed their API at some point. I'm on Ubuntu 10.04 and have glib.threads_init(). (pygtk 2.17.0, pygobject 2.15.3)
vanza
JanC
@vanza: Ubuntu 10.04 has `python-gobject` version 2.21.1
JanC
Yeah, you're right - I was looking at the dependencies for pygtk and not the actual pygobject package.
vanza
A: 

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:

  1. Call glib.threads_init() or gobject.threads_init(), whichever you have, as discussed in vanza's answer.
  2. Call 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.
  3. Bracket your call to gtk.main() between gtk.gdk.threads_enter() and gtk.gdk.threads_leave().
  4. 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() 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:

ptomato
While the example is obviously quite redundant, as the question states, it's a minimal example of a larger problem. The actual program has a background thread doing work, and needs to update a GUI progress bar. The GTK docs seem to say that you should not go about screwing with the GUI is any old thread, so I'm dispatching a call back to the main thread with `idle_add` which, as my understanding goes, runs in the thread that's polling that mainloop - the main thread, in this case.
Thanatos
I'm sorry my answer wasn't useful to you (I didn't realize the sleep command wasn't part of the real work you wanted to do), but I think you missed my point. I will edit it and try to explain more clearly.
ptomato
A: 

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.

Thanatos
I'm not really familiar with pygtk (but I am with glib), and I'm pretty sure that to just add stuff to the main loop you don't need threads_enter() / threads_leave() - glib does all the necessary locking internally after you call threads_init(). The enter/leave functions seem to be needed if you're actually calling GDK/GTK code in the thread,
vanza
@vanza: Reading the source code, I agree. gdk_threads_lock is just a simple mutex lock/unlock.
Thanatos