Example of the right way to use QThread in PyQt?

前端 未结 2 1208
北荒
北荒 2020-11-27 15:04

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

相关标签:
2条回答
  • 2020-11-27 15:45

    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_())
    
    0 讨论(0)
  • 2020-11-27 15:56

    You are right that it is a good thing to have a worker thread doing the processing while main thread is doing the GUI. Also, PyQt is providing thread instrumentation with a signal/slot mechanism that is thread safe.

    This may sound of interest. In their example, they build a GUI

    import sys, time
    from PyQt4 import QtCore, QtGui
    
    class MyApp(QtGui.QWidget):
     def __init__(self, parent=None):
      QtGui.QWidget.__init__(self, parent)
    
      self.setGeometry(300, 300, 280, 600)
      self.setWindowTitle('threads')
    
      self.layout = QtGui.QVBoxLayout(self)
    
      self.testButton = QtGui.QPushButton("test")
      self.connect(self.testButton, QtCore.SIGNAL("released()"), self.test)
      self.listwidget = QtGui.QListWidget(self)
    
      self.layout.addWidget(self.testButton)
      self.layout.addWidget(self.listwidget)
    
     def add(self, text):
      """ Add item to list widget """
      print "Add: " + text
      self.listwidget.addItem(text)
      self.listwidget.sortItems()
    
     def addBatch(self,text="test",iters=6,delay=0.3):
      """ Add several items to list widget """
      for i in range(iters):
       time.sleep(delay) # artificial time delay
       self.add(text+" "+str(i))
    
     def test(self):
      self.listwidget.clear()
      # adding entries just from main application: locks ui
      self.addBatch("_non_thread",iters=6,delay=0.3)
    

    (simple ui containing a list widget which we will add some items to by clicking a button)

    You may then create our own thread class, one example is

    class WorkThread(QtCore.QThread):
     def __init__(self):
      QtCore.QThread.__init__(self)
    
     def __del__(self):
      self.wait()
    
     def run(self):
      for i in range(6):
       time.sleep(0.3) # artificial time delay
       self.emit( QtCore.SIGNAL('update(QString)'), "from work thread " + str(i) )
    
      self.terminate()
    

    You do redefine the run() method. You may find an alternative to terminate(), see the tutorial.

    0 讨论(0)
提交回复
热议问题