Below code is getting struck after the declaration og gtkLoopTHread itself, even though I am not giving gtkMainLoop.start() function call. I want to run the timer in background and do some stuff in that UI and destroy the UI once the timer reaches '0'.
Code
def createCountDown(maxSec):
while maxSec > 0:
print maxSec;
maxSec = maxSec - 1;
gtk.main_quit();
return;
maxSec = 5;
gtkLoopThread = threading.Thread(group=None, target=gtk.main, name=None, args=(), kwargs={});
print "After gtkThread declaration"
myThread = threading.Thread(group=None, target=createCountDown, name=None, args=(maxSec), kwargs={});
gtkLoopThread.start();
myThread.start();
Expected Output:
After gtkThread declaration
5
4
3
2
1
Current behaviour: Line 'After gtkThread declaration' print is not seen since gtkLoopThread starts immediately after initializing gtkLoopThread variable
There are several issues that prevent your code from running correctly.
First and foremost, you are missing the call to gobject.threads_init()
(after import gobject
) at the beginning of your program. This call causes PyGTK to release the GIL when entering the main loop. Failure to call it will make threads useless. (In the latest PyGObject with Python 3, this is no longer necessary.)
The second problem, and this one is trivial, is that you are incorrectly constructing a one-element tuple for use as the args
argument to the threading.Thread
constructor - it should be args=(maxSec,)
, not args=(maxSec)
.
After the above two changes, your program displays the expected output.
Finally, as andlabs points out and the documentation confirms, it is not allowed to call GTK code from any thread other than the thread that runs the main loop. Instead of invoking GTK functions, the worker threads can use gobject.idle_add
to schedule code to be executed in the GUI thread. (If the GUI thread is idle, as it should be, this will happen promptly.) In other words, you should replace gtk.main_quit()
with gobject.idle_add(gtk.main_quit)
. If the or function takes arguments, use gobject.idle_add(lambda: gtk_call_here(...)
instead. While it is perfectly legitimate to create multiple worker threads in a GTK program, they must stay clear of direct calls into GTK.
Also, according to the documentation, the thread that runs the main loop should be the same thread that initializes GTK. Thus a correct program should not import gtk
(which initializes it) before the GUI thread is started. Here is a version of your code that implements this:
import threading
# Event marking the GUI thread as having imported GTK. GTK must not
# be imported before this event's flag is set.
gui_ready = threading.Event()
def run_gui_thread():
import gobject
gobject.threads_init()
import gtk
gui_ready.set()
gtk.main()
gui_thread = threading.Thread(target=run_gui_thread)
gui_thread.start()
# wait for the GUI thread to initialize GTK
gui_ready.wait()
# it is now safe to import GTK-related stuff
import gobject, gtk
def countdown(maxSec):
while maxSec > 0:
print maxSec
maxSec -= 1
gobject.idle_add(gtk.main_quit)
worker = threading.Thread(target=countdown, args=(5,))
print 'starting work...'
worker.start()
# When both the worker and gui_thread finish, the main thread
# will exit as well.