An Example
In Listing 2, we re-create the previous example, but now using PyQt threads and no Queue. This is more tricky because the threads try to send events directly to the GUI. The QThread class has the special function postEvent() for that, and every QObject class has the function customEvent(), which you must reimplement to catch those events.
Listing 2: Qt Threads and Event Posting
import sys, time, random from qt import * rand = random.Random() class WorkerThread(QThread): def __init__(self, name, receiver): QThread.__init__(self) self.name = name self.receiver = receiver self.stopped = 0 def run(self): while not self.stopped: time.sleep(rand.random() * 0.3) msg = rand.random() event = QCustomEvent(10000) event.setData("%s: %f" % (self.name, msg)) QThread.postEvent(self.receiver, event) def stop(self): self.stopped = 1 class ThreadExample(QMultiLineEdit): def __init__(self, *args): QMultiLineEdit.__init__(self, *args) self.setCaption("Threading Example") self.threads = [] for name in ["t1", "t2", "t3"]: t = WorkerThread(name, self) t.start() self.threads.append(t) def customEvent(self,event): if event.type() == 10000: s = event.data() self.append(s) def __del__(self): for t in self.threads: running = t.running() t.stop() if not t.finished(): t.wait() app = QApplication(sys.argv) threadExample = ThreadExample() app.setMainWidget(threadExample) threadExample.show() sys.exit(app.exec_loop())
As you can see, the code in Listing 2 is considerably more simple than the previous code, even if we have to carefully wait() on the running threads before exiting the application. If you remove the __del__() method, you can be sure of getting a segmentation fault every so often, or something like:
boud@calcifer:~/doc/articles/informit/threading> python qtthreads.py Traceback (most recent call last): File "qtthreads.py", line 20, in run event = QCustomEvent(10000) TypeError: 'NoneType' object is not callable Segmentation fault
Another interesting thing to note is that you cannot stop a thread forcibly. No, you cannot. Java used to have a function for that, but it's deprecated, and no Python threading library offers anything like kill for processes. The reason is that a thread can access shared data and leave that data in an undefined state when the thread is stopped. You must do the graceful thing: Set a stop flag that is checked in the run() method. If your thread hangs somewhere in the run() method, you've got a basically insoluble problem. Be aware of this.
Now we'll modify the WorkerThread class so that the threads can directly mess with the state of the GUI. To do this, they must first acquire a lock on the GUI thread, and that's done with the qApp.lock() method:
class WorkerThread(QThread): def __init__(self, name, receiver): QThread.__init__(self) self.name = name self.receiver = receiver self.stopped = 0 def run(self): while not self.stopped: time.sleep(rand.random() * 0.3) msg = rand.random() qApp.lock() self.receiver.append("%s: %f" % (self.name, msg)) qApp.unlock() def stop(self): self.stopped = 1
The qApp object is the singleton instance of QApplication that can be accessed globally in your application.