Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

gtk idle_add not running?

Tags:

python

pygtk

gtk

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.

like image 784
Thanatos Avatar asked Feb 26 '23 09:02

Thanatos


2 Answers

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.

like image 98
vanza Avatar answered Mar 07 '23 22:03

vanza


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:

  • http://www.yolinux.com/TUTORIALS/GDK_Threads.html
  • http://blogs.operationaldynamics.com/andrew/software/gnome-desktop/gtk-thread-awareness.html
like image 26
ptomato Avatar answered Mar 07 '23 20:03

ptomato