Exemple de la bonne façon d'utiliser QThread dans PyQt?

j'essaie d'apprendre à utiliser QThreads dans une application GUI PyQt. J'ai des choses qui tournent pendant un certain temps, avec (habituellement) des points où je pourrais mettre à jour une interface graphique, mais je voudrais partager le travail principal sur son propre thread (parfois les choses sont bloquées, et il serait agréable d'avoir éventuellement un bouton Annuler/essayer à nouveau, ce qui ne fonctionne évidemment pas si l'interface graphique est gelée parce que la boucle principale est bloquée).

j'ai lu https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/. Cette page dit que re-implémenter le run méthode n'est pas la façon de le faire. Le problème que j'ai est de trouver un exemple PyQt qui a un fil principal faisant L'interface graphique et un fil ouvrier qui ne le fait pas de cette façon. Le billet de blog est pour C++, donc bien que les exemples aident, je suis encore un peu perdu. Quelqu'un peut-il svp m'indiquer un exemple de la bonne façon de le faire dans Python?

25
demandé sur Azendale 2013-06-02 09:09:42

2 réponses

voici un exemple fonctionnel d'un thread de worker séparé qui peut envoyer et recevoir des signaux pour lui permettre de communiquer avec une interface graphique.

j'ai fait deux boutons simples, un qui commence un long calcul dans un thread séparé, et un qui termine immédiatement le calcul et réinitialise le worker thread.

mettre fin de force à un fil comme c'est fait ici n'est généralement pas la meilleure façon de faire les choses, mais il ya des situations dans lesquelles toujours sortie gracieusement n'est pas un 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_())
8
répondu amicitas 2018-06-25 16:40:59

vous avez raison que c'est une bonne chose d'avoir un worker thread qui fait le traitement pendant que le thread principal fait l'interface graphique. En outre, PyQt fournit une instrumentation de thread avec un signal/mécanisme de fente qui est sûr de thread.

Cela peut sembler d'intérêt. Dans leur exemple, ils construisent une interface graphique!--5-->

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 contenant un widget de liste auquel nous ajouterons quelques éléments en cliquant sur un bouton)

vous pouvez alors créer notre propre classe de thread, un exemple

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()

Vous n'avez redéfinir l' run() méthode. Vous pouvez trouver des alternatives à terminate(), voir le tutoriel.

1
répondu octoback 2013-06-02 06:21:17