As someone new to GUI development in Python (with pyGTK), I’ve just started learning about threading. To test out my skills, I’ve written a simple little GTK interface with a start/stop button. The goal is that when it is clicked, a thread starts that quickly increments a number in the text box, while keeping the GUI responsive.
I’ve got the GUI working just fine, but am having problems with the threading. It is probably a simple problem, but my mind is about fried for the day. Below I have pasted first the trackback from the Python interpreter, followed by the code. You can go to http://drop.io/pxgr5id to download it. I’m using bzr for revision control, so if you want to make a modification and re-drop it, please commit the changes. I’m also pasting the code at http://dpaste.com/113388/ because it can have line numbers, and this markdown stuff is giving me a headache.
Update 27 January, 15:52 EST: Slightly updated code can be found here: http://drop.io/threagui/asset/thread-gui-rev3-tar-gz
Traceback
crashsystems@crashsystems-laptop:~/Desktop/thread-gui$ python threadgui.pybtnStartStop clicked Traceback (most recent call last): File 'threadgui.py', line 39, in on_btnStartStop_clicked self.thread.stop() File 'threadgui.py', line 20, in stop self.join() File '/usr/lib/python2.5/threading.py', line 583, in join raise RuntimeError('cannot join thread before it is started') RuntimeError: cannot join thread before it is started btnStartStop clicked threadStop = 1 btnStartStop clicked threadStop = 0 btnStartStop clicked Traceback (most recent call last): File 'threadgui.py', line 36, in on_btnStartStop_clicked self.thread.start() File '/usr/lib/python2.5/threading.py', line 434, in start raise RuntimeError('thread already started') RuntimeError: thread already started btnExit clicked exit() called
Code
#!/usr/bin/bash import gtk, threading class ThreadLooper (threading.Thread): def __init__ (self, sleep_interval, function, args=[], kwargs={}): threading.Thread.__init__(self) self.sleep_interval = sleep_interval self.function = function self.args = args self.kwargs = kwargs self.finished = threading.Event() def stop (self): self.finished.set() self.join() def run (self): while not self.finished.isSet(): self.finished.wait(self.sleep_interval) self.function(*self.args, **self.kwargs) class ThreadGUI: # Define signals def on_btnStartStop_clicked(self, widget, data=None): print 'btnStartStop clicked' if(self.threadStop == 0): self.threadStop = 1 self.thread.start() else: self.threadStop = 0 self.thread.stop() print 'threadStop = ' + str(self.threadStop) def on_btnMessageBox_clicked(self, widget, data=None): print 'btnMessageBox clicked' self.lblMessage.set_text('This is a message!') self.msgBox.show() def on_btnExit_clicked(self, widget, data=None): print 'btnExit clicked' self.exit() def on_btnOk_clicked(self, widget, data=None): print 'btnOk clicked' self.msgBox.hide() def on_mainWindow_destroy(self, widget, data=None): print 'mainWindow destroyed!' self.exit() def exit(self): print 'exit() called' self.threadStop = 1 gtk.main_quit() def threadLoop(self): # This will run in a thread self.txtThreadView.set_text(str(self.threadCount)) print 'hello world' self.threadCount += 1 def __init__(self): # Connect to the xml GUI file builder = gtk.Builder() builder.add_from_file('threadgui.xml') # Connect to GUI widgets self.mainWindow = builder.get_object('mainWindow') self.txtThreadView = builder.get_object('txtThreadView') self.btnStartStop = builder.get_object('btnStartStop') self.msgBox = builder.get_object('msgBox') self.btnMessageBox = builder.get_object('btnMessageBox') self.btnExit = builder.get_object('btnExit') self.lblMessage = builder.get_object('lblMessage') self.btnOk = builder.get_object('btnOk') # Connect the signals builder.connect_signals(self) # This global will be used for signaling the thread to stop. self.threadStop = 1 # The thread self.thread = ThreadLooper(0.1, self.threadLoop, (1,0,-1)) self.threadCounter = 0 if __name__ == '__main__': # Start GUI instance GUI = ThreadGUI() GUI.mainWindow.show() gtk.main()
Threading with PyGTK is bit tricky if you want to do it right. Basically, you should not update GUI from within any other thread than main thread (common limitation in GUI libs). Usually this is done in PyGTK using mechanism of queued messages (for communication between workers and GUI) which are read periodically using timeout function. Once I had a presentation on my local LUG on this topic, you can grab example code for this presentation from Google Code repository. Have a look at
MainWindowclass informs/frmmain.py, specially for method_pulse()and what is done inon_entry_activate()(thread is started there plus the idle timer is created).This way, application updates GUI when is ‘idle’ (by GTK means) causing no freezes.