2017-08-07 4 views
0

J'essaye de mettre à jour un QProgressBar de pyqt à partir de plusieurs threads, et d'après ce que je comprends, la meilleure façon d'y parvenir est d'émettre des signaux dans le thread graphique principal (j'ai essayé de passer l'objet QProgressBar aux fils de travail et bien que cela semble fonctionner, j'ai reçu une tonne d'avertissements dans l'interprète). Dans le code suivant, j'ai mis en place un signal progressSignal et je l'ai connecté à un thread qui (pour l'instant) imprime tout ce qui a été émis. J'émets ensuite de chaque fil le pourcentage total. Je sais que cela fonctionne en dehors des threads en jetant juste une émission aléatoire dans la ligne 47, qui vient à travers. Cependant, la ligne 36 Emettre à partir ne déclenche rien, il ne semble jamais le faire par ...pyqt émet un signal à partir du thread de thread

import Queue, threading 
from PyQt4 import QtCore 
import shutil 
import profile 

fileQueue = Queue.Queue() 

class Communicate(QtCore.QObject): 

    progressSignal = QtCore.pyqtSignal(int) 

class ThreadedCopy: 
    totalFiles = 0 
    copyCount = 0 
    lock = threading.Lock() 

    def __init__(self, inputList, progressBar="Undefined"): 
     self.totalFiles = len(inputList) 

     self.c = Communicate() 
     self.c.progressSignal.connect(self.updateProgressBar) 

     print str(self.totalFiles) + " files to copy." 
     self.threadWorkerCopy(inputList) 


    def CopyWorker(self): 
     while True: 
      self.c.progressSignal.emit(2000) 
      fileName = fileQueue.get() 
      shutil.copy(fileName[0], fileName[1]) 
      fileQueue.task_done() 
      with self.lock: 
       self.copyCount += 1 
       percent = (self.copyCount * 100)/self.totalFiles 
       self.c.progressSignal.emit(percent) 

    def threadWorkerCopy(self, fileNameList): 

     for i in range(16): 
      t = threading.Thread(target=self.CopyWorker) 
      t.daemon = True 
      t.start() 
     for fileName in fileNameList: 
      fileQueue.put(fileName) 
     fileQueue.join() 
     self.c.progressSignal.emit(1000) 

    def updateProgressBar(self, percent): 
     print percent 

MISE À JOUR:

Heres un échantillon avec une interface utilisateur graphique. Celui-ci fonctionne, mais est assez instable, il se bloque régulièrement et l'interface utilisateur fait des trucs bizarres (barre de progression ne pas terminer, etc.)

Main.py:

import sys, os 
import MultithreadedCopy_5 
from PyQt4 import QtCore, QtGui 

def grabFiles(path): 
    # gets all files (not folders) in a directory 
    for file in os.listdir(path): 
     if os.path.isfile(os.path.join(path, file)): 
      yield os.path.join(path, file) 

class MainWin(QtGui.QWidget): 

    def __init__(self): 
     super(MainWin, self).__init__() 
     self.initUI() 

    def initUI(self): 
     self.progress = QtGui.QProgressBar() 

     box = QtGui.QVBoxLayout() 
     box.addWidget(self.progress) 
     goBtn = QtGui.QPushButton("Start copy") 
     box.addWidget(goBtn) 

     self.setLayout(box) 

     goBtn.clicked.connect(self.startCopy) 

    def startCopy(self): 
     files = grabFiles("folder/with/files") 
     fileList = [] 
     for file in files: 
      fileList.append([file,"folder/to/copy/to"]) 

     MultithreadedCopy_5.ThreadedCopy(fileList, self.progress) 

def main(): 
    app = QtGui.QApplication(sys.argv) 
    ex = MainWin() 
    ex.show() 
    sys.exit(app.exec_()) 

if __name__ == "__main__": 
    main() 

MultithreadedCopy_5.py:

import Queue, threading 
from PyQt4 import QtCore 
import shutil 
import profile 

fileQueue = Queue.Queue() 

class Communicate(QtCore.QObject): 

    progressSignal = QtCore.pyqtSignal(int) 

class ThreadedCopy: 
    totalFiles = 0 
    copyCount = 0 
    lock = threading.Lock() 

    def __init__(self, inputList, progressBar="Undefined"): 
     self.progressBar = progressBar 
     self.totalFiles = len(inputList) 

     self.c = Communicate() 
     self.c.progressSignal.connect(self.updateProgressBar, QtCore.Qt.DirectConnection) 

     print str(self.totalFiles) + " files to copy." 
     self.threadWorkerCopy(inputList) 


    def CopyWorker(self): 
     while True: 
      fileName = fileQueue.get() 
      shutil.copy(fileName[0], fileName[1]) 
      fileQueue.task_done() 
      with self.lock: 
       self.copyCount += 1 
       percent = (self.copyCount * 100)/self.totalFiles 
       self.c.progressSignal.emit(percent) 

    def threadWorkerCopy(self, fileNameList): 
     for i in range(16): 
      t = threading.Thread(target=self.CopyWorker) 
      t.daemon = True 
      t.start() 
     for fileName in fileNameList: 
      fileQueue.put(fileName) 
     fileQueue.join() 

    def updateProgressBar(self, percent): 
     self.progressBar.setValue(percent) 

#profile.run('ThreadedCopy()') 
+0

Je commence à réaliser que les fils de python ne peut pas émettre des signaux à la principale application de PyQt, de sorte que la façon de le faire « correcte » serait avec PyQt qthreads - que je préférerais ne pas faire. Y aurait-il un moyen facile d'émettre le signal dans le thread principal chaque fois qu'un thread de travail se termine? – Spencer

+0

S'il vous plaît voir ma réponse pour une solution qui devrait au moins résoudre les problèmes avec les exemples indiqués dans votre question. – ekhumoro

Répondre

-2

le principal problème est le délai écoulé entre l'envoi du signal et la réception, on peut réduire ce temps à l'aide processEvents():

Vous pouvez appeler cette fonction occasionnellement lorsque votre programme est occupé en effectuant une opération longue (par ex. copier un fichier).

def CopyWorker(self): 
    while True: 
     fileName = fileQueue.get() 
     shutil.copy(fileName[0], fileName[1]) 
     fileQueue.task_done() 
     with self.lock: 
      self.copyCount += 1 
      print(self.copyCount) 
      percent = (self.copyCount * 100)/self.totalFiles 
      self.c.progressSignal.emit(percent) 
      QtCore.QCoreApplication.processEvents() 
+0

Parfait! Merci, c'est juste ce dont j'avais besoin. – Spencer

+0

Pourquoi la downvote? – eyllanesc

+0

"La fente est exécutée dans le fil de signalisation." Cela ne sonne pas bien. Les éléments GUI ne devraient-ils être modifiés que par le thread graphique? – Trilarion

1

Il y a deux problèmes principaux avec vos exemples. Premièrement, l'objet qui émet les signaux est créé dans le thread principal/gui, de sorte que tous les signaux qu'il émet ne seront pas des threads croisés, et donc pas thread-safe. La solution évidente à cela est de créer l'objet de signalisation à l'intérieur de la fonction cible du thread de travail - ce qui signifie qu'il doit y avoir une instance distincte pour chaque thread.

Deuxièmement, la boucle while à l'intérieur de la fonction cible n'est jamais terminée, ce qui signifie que tous les objets ThreadedCopy seront maintenus en vie une fois l'opération de copie en cours terminée. Comme tous ces objets partagent la même file d'attente, le comportement deviendra imprévisible si l'on tente de répéter l'opération de copie. La solution évidente à cela est de sortir de la boucle while une fois la file d'attente vide. Voici une réécriture de MultithreadedCopy_5.py qui devrait résoudre ces problèmes. Cependant, comme indiqué dans les commentaires, je recommande fortement d'utiliser QThread plutôt que des threads python dans ce scénario, car il est susceptible de fournir une solution beaucoup plus robuste et plus facilement maintenable.

import Queue, threading 
from PyQt4 import QtCore 
import shutil 
import profile 

fileQueue = Queue.Queue() 

class Communicate(QtCore.QObject): 
    progressSignal = QtCore.pyqtSignal(int) 

class ThreadedCopy: 
    totalFiles = 0 
    copyCount = 0 
    lock = threading.Lock() 

    def __init__(self, inputList, progressBar="Undefined"): 
     self.progressBar = progressBar 
     self.totalFiles = len(inputList) 
     print str(self.totalFiles) + " files to copy." 
     self.threadWorkerCopy(inputList) 

    def CopyWorker(self): 
     c = Communicate() 
     c.progressSignal.connect(self.updateProgressBar) 
     while True: 
      try: 
       fileName = fileQueue.get(False) 
      except Queue.Empty: 
       break 
      else: 
       shutil.copy(fileName[0], fileName[1]) 
       with self.lock: 
        self.copyCount += 1 
        percent = (self.copyCount * 100)/self.totalFiles 
        c.progressSignal.emit(percent) 
       fileQueue.task_done() 

    def threadWorkerCopy(self, fileNameList): 
     if fileQueue.empty(): 
      for i in range(16): 
       t = threading.Thread(target=self.CopyWorker) 
       t.daemon = True 
       t.start() 
      for fileName in fileNameList: 
       fileQueue.put(fileName) 
      fileQueue.join() 

    def updateProgressBar(self, percent): 
     self.progressBar.setValue(percent)