J'essaie d'apprendre à utiliser QThreads dans une application PyQt Gui. J'ai des trucs qui fonctionnent pendant un certain temps, avec (généralement) des points où je pourrais mettre à jour un Gui, mais je voudrais diviser le travail principal sur son propre thread (parfois des trucs se bloquent, et ce serait bien d'avoir éventuellement un bouton annuler/réessayer, ce qui ne fonctionne évidemment pas si le Gui est gelé car 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 indique que la réimplémentation de la méthode run
n'est pas la façon de le faire. Le problème que j'ai est de trouver un exemple PyQt qui a un thread principal faisant le Gui et un thread de travail qui ne le fait pas de cette façon. Le blog est pour C++, alors même si ses exemples aident, je suis toujours un peu perdu. Quelqu'un peut-il me montrer un exemple de la bonne façon de le faire en Python?
Voici un exemple de travail d'un thread de travail distinct qui peut envoyer et recevoir des signaux pour lui permettre de communiquer avec une interface graphique.
J'ai créé deux boutons simples, un qui démarre un long calcul dans un thread séparé, et un qui termine immédiatement le calcul et réinitialise le thread de travail.
Terminer de force un thread comme cela est fait ici n'est généralement pas la meilleure façon de faire les choses, mais il y a des situations dans lesquelles toujours quitter gracieusement n'est pas une 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_())
Vous avez raison de dire que c'est une bonne chose d'avoir un thread de travail effectuant le traitement tandis que le thread principal fait l'interface graphique. De plus, PyQt fournit une instrumentation de thread avec un mécanisme de signal/slot qui est thread-safe.
Cela peut sembler intéressant . Dans leur exemple, ils construisent une interface graphique
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)
(interface utilisateur simple contenant un widget de liste auquel nous ajouterons des éléments en cliquant sur un bouton)
Vous pouvez ensuite créer notre propre classe de threads, un exemple est
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 redéfinissez la méthode run()
. Vous pouvez trouver une alternative à terminate()
, voir le tutoriel.