Like this article? We recommend

Like this article? We recommend

Native Threads and a GUI

If you think your application might one day get a different GUI but will
remain firmly rooted in Python, using Python's native threading support is
a good bet. You can use Python's native threads with any GUI library you
like, but you should not use more than one threading system in the same
application. That means that you cannot use threading and QThread at
the same moment. Doing so will invariably result in crashes.

In a GUI application that uses native threads, it is also not possible to
directly influence the GUI thread. PyQt's thread support and
wxPython's thread support offer functions you can use to send messages or
events to the GUI thread or to lock the main GUI before accessing shared data.

If you use Python's native thread, your worker threads must fill a
Queue object and your GUI thread must install a timer that every so
often polls the Queue and retrieves new data.

Let's briefly look at Listing 1, a small recipe that uses PyQt, derived
from a similar script by Jacob Hallen (from the ActiveStatePython
Cookbook).

Listing 1: Using Python Threads and a PyQt GUI

import sys, time, threading, random, Queue, qt
class GuiPart(qt.QMainWindow):
def __init__(self, queue, endcommand, *args):
qt.QMainWindow.__init__(self, *args)
self.queue = queue
self.editor = qt.QMultiLineEdit(self)
self.setCentralWidget(self.editor)
self.endcommand = endcommand
def closeEvent(self, ev):
"""
We just call the endcommand when the window is closed
instead of presenting a button for that purpose.
"""
self.endcommand()
def processIncoming(self):
"""
Handle all the messages currently in the queue (if any).
"""
while self.queue.qsize():
try:
msg = self.queue.get(0)
# Check contents of message and do what it says
# As a test, we simply print it
self.editor.insertLine(str(msg))
except Queue.Empty:
pass
class ThreadedClient:
"""
Launch the main part of the GUI and the worker thread.
periodicCall and endApplication could reside in the GUI part,
but putting them here means that you have all the thread
controls in a single place.
"""
def __init__(self):
# Create the queue
self.queue = Queue.Queue()
# Set up the GUI part
self.gui=GuiPart(self.queue, self.endApplication)
self.gui.show()
# A timer to periodically call periodicCall :-)
self.timer = qt.QTimer()
qt.QObject.connect(self.timer,
qt.SIGNAL("timeout()"),
self.periodicCall)
# Start the timer -- this replaces the initial call
# to periodicCall
self.timer.start(100)
# Set up the thread to do asynchronous I/O
# More can be made if necessary
self.running = 1
_self.thread1 = threading.Thread(target=self.workerThread1)
self.thread1.start()
def periodicCall(self):
"""
Check every 100 ms if there is something new in the queue.
"""
self.gui.processIncoming()
if not self.running:
root.quit()
def endApplication(self):
self.running = 0
def workerThread1(self):
"""
This is where we handle the asynchronous I/O. For example,
it may be a 'select()'.
One important thing to remember is that the thread has to
yield control.
"""
while self.running:
# To simulate asynchronous I/O, we create a random number
# at random intervals. Replace the following 2 lines
# with the real thing.
time.sleep(rand.random() * 0.3)
msg = rand.random()
self.queue.put(msg)
rand = random.Random()
root = qt.QApplication(sys.argv)
client = ThreadedClient()
root.exec_loop()

Important in this example are the queue and the timer. Every time the QTimer
ticks, a signal is sent to the periodicCall method. This method asks
the GUI to process everything that has been put in the Queue by one of
the worker threads.

This example is a direct translation of the Tkinter example given in the
ActiveState Python Cookbook, and it shows how easy it is to adapt
threaded Tkinter code to PyQt.