Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Example of the right way to use QThread in PyQt?

I'm trying to learn how to use QThreads in a PyQt Gui application. I have stuff that runs for a while, with (usually) points where I could update a Gui, but I would like to split the main work out to its own thread (sometimes stuff gets stuck, and it would be nice to eventually have a cancel/try again button, which obviously doesn't work if the Gui is frozen because the Main Loop is blocked).

I've read https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/. That page says that re-implementing the run method is not the way to do it. The problem I am having is finding a PyQt example that has a main thread doing the Gui and a worker thread that does not do it that way. The blog post is for C++, so while it's examples do help, I'm still a little lost. Can someone please point me to an example of the right way to do it in Python?

like image 712
Azendale Avatar asked Jun 02 '13 05:06

Azendale


People also ask

Is PyQt thread safe?

While some parts of the Qt framework are thread safe, much of it is not. The Qt C++ documentation provides a good overview of which classes are reentrant (can be used to instantiate objects in multiple threads).


1 Answers

Here is a working example of a separate worker thread which can send and receive signals to allow it to communicate with a GUI.

I made two simple buttons, one which starts a long calculation in a separate thread, and one which immediately terminates the calculation and resets the worker thread.

Forcibly terminating a thread as is done here is not generally the best way to do things, but there are situations in which always gracefully exiting is not an option.

from PyQt4 import QtGui, QtCore import sys import random  class Example(QtCore.QObject):      signalStatus = QtCore.pyqtSignal(str)      def __init__(self, parent=None):         super(self.__class__, self).__init__(parent)          # Create a gui object.         self.gui = Window()          # Create a new worker thread.         self.createWorkerThread()          # Make any cross object connections.         self._connectSignals()          self.gui.show()       def _connectSignals(self):         self.gui.button_cancel.clicked.connect(self.forceWorkerReset)         self.signalStatus.connect(self.gui.updateStatus)         self.parent().aboutToQuit.connect(self.forceWorkerQuit)       def createWorkerThread(self):          # Setup the worker object and the worker_thread.         self.worker = WorkerObject()         self.worker_thread = QtCore.QThread()         self.worker.moveToThread(self.worker_thread)         self.worker_thread.start()          # Connect any worker signals         self.worker.signalStatus.connect(self.gui.updateStatus)         self.gui.button_start.clicked.connect(self.worker.startWork)       def forceWorkerReset(self):               if self.worker_thread.isRunning():             print('Terminating thread.')             self.worker_thread.terminate()              print('Waiting for thread termination.')             self.worker_thread.wait()              self.signalStatus.emit('Idle.')              print('building new working object.')             self.createWorkerThread()       def forceWorkerQuit(self):         if self.worker_thread.isRunning():             self.worker_thread.terminate()             self.worker_thread.wait()   class WorkerObject(QtCore.QObject):      signalStatus = QtCore.pyqtSignal(str)      def __init__(self, parent=None):         super(self.__class__, self).__init__(parent)      @QtCore.pyqtSlot()             def startWork(self):         for ii in range(7):             number = random.randint(0,5000**ii)             self.signalStatus.emit('Iteration: {}, Factoring: {}'.format(ii, number))             factors = self.primeFactors(number)             print('Number: ', number, 'Factors: ', factors)         self.signalStatus.emit('Idle.')      def primeFactors(self, n):         i = 2         factors = []         while i * i <= n:             if n % i:                 i += 1             else:                 n //= i                 factors.append(i)         if n > 1:             factors.append(n)         return factors   class Window(QtGui.QWidget):      def __init__(self):         QtGui.QWidget.__init__(self)         self.button_start = QtGui.QPushButton('Start', self)         self.button_cancel = QtGui.QPushButton('Cancel', self)         self.label_status = QtGui.QLabel('', self)          layout = QtGui.QVBoxLayout(self)         layout.addWidget(self.button_start)         layout.addWidget(self.button_cancel)         layout.addWidget(self.label_status)          self.setFixedSize(400, 200)      @QtCore.pyqtSlot(str)     def updateStatus(self, status):         self.label_status.setText(status)   if __name__=='__main__':     app = QtGui.QApplication(sys.argv)     example = Example(app)     sys.exit(app.exec_()) 
like image 109
amicitas Avatar answered Oct 05 '22 23:10

amicitas