2013-03-29 7 views
14

Quali sono i modi migliori per creare un sistema di codifica GUI multiprocessing?schemi di GUI multiprocessing per combattere il blocco "Non risponde"

Vorrei creare un posto per la comunità di internet e trovare esempi su come utilizzare il modulo multiprocessing in python.

Ho visto diversi piccoli esempi di processi multiprocessing su internet di semplici funzioni globali che sono chiamate in un modulo principale, ma ho trovato che questo si traduce raramente facilmente in qualsiasi cosa che chiunque faccia effettivamente riguardo alle GUI. Penserei che molti programmi avrebbero le funzioni che vogliono usare in un processo separato come metodi di oggetti (che possono essere aggregati di altri oggetti, ecc.) E forse un singolo elemento della GUI avrebbe un oggetto associato che deve chiamare questo processo, ecc

per esempio, io ho un programma relativamente complessa e sto avendo problemi ad ottenere una GUI reattivo per esso, che ho creduto di essere a causa della mia mancanza di comprensione in multiprocessing e filettatura con QThread. Tuttavia, so che l'esempio riportato di seguito almeno trasferirà le informazioni tra i processi nel modo in cui desidero (poiché è in grado di eseguire le istruzioni print) ma la mia GUI sta ancora bloccando. Qualcuno sa che cosa potrebbe causare questo, e se è ancora un problema con la mia mancanza di comprensione in architetture mutlithreaded/multiprocessing?

Ecco un piccolo esempio di codice pseudo di quello che sto facendo:

class Worker: 
    ... 
    def processing(self, queue): 
     # put stuff into queue in a loop 

# This thread gets data from Worker 
class Worker_thread(QThread): 
    def __init__(self): 
     ... 
     # make process with Worker inside 
    def start_processing(self): 
     # continuously get data from Worker 
     # send data to Tab object with signals/slots 

class Tab(QTabWidget): 
    # spawn a thread separate from main GUI thread 

    # update GUI using slot 
    def update_GUI() 

e questo codice è esempio completamente compilabile che incarna lo sturcture sovrastante del mio programma:

from PyQt4 import QtCore, QtGui 
import multiprocessing as mp 
import numpy as np 
import sys 
import time 

# This object can hold several properties which will be used for the processing 
# and will be run in the background, while it updates a thread with all of it's progress 
class Worker: 
    def __init__(self, some_var): 
     self.some_var = some_var 
     self.iteration = 0 

    def some_complex_processing(self, queue): 
     for i in range(0,5000): 
      self.iteration += 1 
      queue.put(self.iteration) 
     queue.put('done with processing') 

# This Woker_thread is a thread which will spawn a separate process (Worker). 
# This separate is needed in order to separate the data retrieval 
# from the main GUI thread, which should only quickly update when needed 
class Worker_thread(QtCore.QThread): 
    # signals and slots are used to communicate back to the main GUI thread 
    update_signal = QtCore.pyqtSignal(int) 
    done_signal = QtCore.pyqtSignal() 

    def __init__(self, parent, worker): 
     QtCore.QThread.__init__(self, parent) 
     self.queue = mp.Queue() 
     self.worker = worker 
     self.parent = parent 
     self.process = mp.Process(target=self.worker.some_complex_processing, args=(self.queue,)) 

    # When the process button is pressed, this function will start getting data from Worker 
    # this data is then retrieved by the queue and pushed through a signal 
    # to Tab.update_GUI 
    @QtCore.pyqtSlot() 
    def start_computation(self): 
     self.process.start() 
     while(True): 
      try: 
       message = self.queue.get() 
       self.update_signal.emit(message) 
      except EOFError: 
       pass 
      if message == 'done with processing': 
       self.done_signal.emit() 
       break 
      #self.parent.update_GUI(message) 
     self.process.join() 
     return 

# Each tab will start it's own thread, which will spawn a process 
class Tab(QtGui.QTabWidget): 
    start_comp = QtCore.pyqtSignal() 
    def __init__(self, parent, this_worker): 
     self.parent = parent 
     self.this_worker = this_worker 
     QtGui.QTabWidget.__init__(self, parent) 

     self.treeWidget = QtGui.QTreeWidget(self) 
     self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"]) 
     self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"]) 

     self.thread = Worker_thread(parent=self, worker=self.this_worker) 
     self.thread.update_signal.connect(self.update_GUI) 
     self.thread.done_signal.connect(self.thread.quit) 
     self.start_comp.connect(self.thread.start_computation) 
     self.thread.start() 

    ############################### 
    # Here is what should update the GUI at every iteration of Worker.some_complex_processing() 
    # The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated. 
    @QtCore.pyqtSlot(int) 
    def update_GUI(self, iteration): 
     self.step.setText(0, str(iteration)) 
     #time.sleep(0.1) 
     print iteration 

    def start_signal_emit(self): 
     self.start_comp.emit() 

# GUI stuff 
class MainWindow(QtGui.QMainWindow): 
    def __init__(self, parent = None): 
     QtGui.QMainWindow.__init__(self) 
     self.tab_list = [] 
     self.setTabShape(QtGui.QTabWidget.Rounded) 
     self.centralwidget = QtGui.QWidget(self) 
     self.top_level_layout = QtGui.QGridLayout(self.centralwidget) 

     self.tabWidget = QtGui.QTabWidget(self.centralwidget) 
     self.top_level_layout.addWidget(self.tabWidget, 1, 0, 25, 25) 

     process_button = QtGui.QPushButton("Process") 
     self.top_level_layout.addWidget(process_button, 0, 1) 
     QtCore.QObject.connect(process_button, QtCore.SIGNAL("clicked()"), self.process) 

     self.setCentralWidget(self.centralwidget) 
     self.centralwidget.setLayout(self.top_level_layout) 

     # Make Tabs in loop from button 
     for i in range(0,10): 
      name = 'tab' + str(i) 
      self.tab_list.append(Tab(self.tabWidget, Worker(name))) 
      self.tabWidget.addTab(self.tab_list[-1], name) 

    # Do the processing 
    def process(self): 
     for tab in self.tab_list: 
      tab.start_signal_emit() 
     return 

if __name__ == "__main__": 
    app = QtGui.QApplication([]) 
    win = MainWindow() 
    win.show() 
    sys.exit(app.exec_()) 

Altro Informazioni: Sto scrivendo un programma che mi piacerebbe generare diversi processi e farli mostrare continuamente i loro progressi attraverso la loro elaborazione. Vorrei che il programma fosse multiprocesso per ottenere la massima velocità possibile dal programma.

Al momento, sto cercando di utilizzare un thread per generare un processo e utilizzare segnali e slot per aggiornare la GUI mentre i dati vengono continuamente recuperati da una coda. Sembra che il queues, signals e slots funzionino quando si utilizzano le istruzioni print, ma non è possibile aggiornare la GUI. Se qualcuno ha altri suggerimenti su come dovrei strutturare questo per mantenere il programma più gestibile, mi piacerebbe imparare.

EDIT: ho fatto le regolazioni messe avanti da Lin Min, con l'aggiunta di fare Worker un QObject in modo che moveToThread() avrebbe funzionato.
Ecco il nuovo codice che ho in questo momento:

from PyQt4 import QtCore, QtGui 
import multiprocessing as mp 
import numpy as np 
import sys 
import time 

class Worker(QtCore.QObject): 
    update_signal = QtCore.pyqtSignal(int) 
    done_signal = QtCore.pyqtSignal() 

    def __init__(self, some_var): 
     QtCore.QObject.__init__(self, parent=None) 
     self.some_var = some_var 
     self.iteration = 0 
     self.queue = mp.Queue() 
     self.process = mp.Process(target=self.some_complex_processing, args=(self.queue,)) 

    def some_complex_processing(self, queue): 
     for i in range(0,5000): 
      self.iteration += 1 
      queue.put(self.iteration) 
     queue.put('done with processing') 

    @QtCore.pyqtSlot() 
    def start_computation(self): 
     self.process.start() 
     while(True): 
      try: 
       message = self.queue.get() 
       self.update_signal.emit(message) 
      except EOFError: 
       pass 
      if message == 'done with processing': 
       self.done_signal.emit() 
       break 
     self.process.join() 
     return 



class Tab(QtGui.QTabWidget): 
    start_comp = QtCore.pyqtSignal() 
    def __init__(self, parent, this_worker): 
     self.parent = parent 
     self.this_worker = this_worker 
     QtGui.QTabWidget.__init__(self, parent) 

     self.treeWidget = QtGui.QTreeWidget(self) 
     self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"]) 
     self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"]) 

     # Use QThread is enough 
     self.thread = QtCore.QThread(); 
     # Change the thread affinity of worker to self.thread. 
     self.this_worker.moveToThread(self.thread); 
     self.this_worker.update_signal.connect(self.update_GUI) 
     self.this_worker.done_signal.connect(self.thread.quit) 
     self.start_comp.connect(self.this_worker.start_computation) 
     self.thread.start() 

    ############################### 
    # Here is what should update the GUI at every iteration of Worker.some_complex_processing() 
    # The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated. 
    @QtCore.pyqtSlot(int) 
    def update_GUI(self, iteration): 
     self.step.setText(0, str(iteration)) 
     #time.sleep(0.1) 
     print iteration 

    def start_signal_emit(self): 
     self.start_comp.emit() 

# GUI stuff 
class MainWindow(QtGui.QMainWindow): 
    def __init__(self, parent = None): 
     QtGui.QMainWindow.__init__(self) 
     self.tab_list = [] 
     self.setTabShape(QtGui.QTabWidget.Rounded) 
     self.centralwidget = QtGui.QWidget(self) 
     self.top_level_layout = QtGui.QGridLayout(self.centralwidget) 

     self.tabWidget = QtGui.QTabWidget(self.centralwidget) 
     self.top_level_layout.addWidget(self.tabWidget, 1, 0, 25, 25) 

     process_button = QtGui.QPushButton("Process") 
     self.top_level_layout.addWidget(process_button, 0, 1) 
     QtCore.QObject.connect(process_button, QtCore.SIGNAL("clicked()"), self.process) 

     self.setCentralWidget(self.centralwidget) 
     self.centralwidget.setLayout(self.top_level_layout) 

     # Make Tabs in loop from button 
     for i in range(0,10): 
      name = 'tab' + str(i) 
      self.tab_list.append(Tab(self.tabWidget, Worker(name))) 
      self.tabWidget.addTab(self.tab_list[-1], name) 

    # Do the processing 
    def process(self): 
     for tab in self.tab_list: 
      tab.start_signal_emit() 
     return 

if __name__ == "__main__": 
    app = QtGui.QApplication([]) 
    win = MainWindow() 
    win.show() 
    sys.exit(app.exec_()) 

Grazie per tutte le risposte, apprezzo il livello di dettaglio che ognuno è andato in nel descrivere l'idea che credono di essere una soluzione, ma sfortunatamente non sono ancora stato in grado di eseguire questi tipi di processi che operano sull'oggetto a cui appartengono mentre si visualizza l'attributo dell'oggetto su una GUI.
Tuttavia, ho imparato una quantità decente da questo post, che mi ha permesso di rendermi conto che la versione con thread che ho al momento è appesa alla GUI poiché la funzione di aggiornamento della GUI è troppo grande e richiede troppa elaborazione.

Quindi, ho preso l'approccio QTimer() alla mia versione multi-thread e sta andando molto meglio! Consiglierei a tutti coloro che affrontano problemi simili di tentare almeno qualcosa di simile a questo.

Non ero a conoscenza di questo approccio per risolvere i problemi di aggiornamento della GUI ed è ora una soluzione pseudo o temporanea al problema che sto affrontando.

+1

Il mio consiglio è di provare e semplificare ancora di più il vostro esempio. Prova a realizzare ciò che stai facendo con un singolo pulsante/elenco/thread. Poi, una volta che hai funzionato, costruisci su di esso. Un po 'come questo articolo http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/ – jross

risposta

7

Un'applicazione GUI è perfetto per le cose di test, come è facile per deporre le uova nuovi compiti e visualizzare ciò che sta accadendo, così ho scritto un piccolo esempio app (Screenshot, il codice è sotto) come volevo impararlo per me stesso.

In un primo momento, ho adottato un approccio simile al tuo, cercando di implementare il modello Consumer/Producer e mi sono battuto con i processi in background che facevano infiniti cicli di attesa per nuovi lavori e mi occupavo della comunicazione avanti e indietro. Poi ho scoperto l'interfaccia Pool e quindi ho potuto sostituire tutto quel codice hidious con poche righe. Tutto ciò che serve è un unico pool e un paio di callback:

#!/usr/bin/env python3 
import multiprocessing, time, random, sys 
from PySide.QtCore import * # equivalent: from PyQt4.QtCore import * 
from PySide.QtGui import * # equivalent: from PyQt4.QtGui import * 

def compute(num): 
    print("worker() started at %d" % num) 
    random_number = random.randint(1, 6) 
    if random_number in (2, 4, 6): 
     raise Exception('Random Exception in _%d' % num) 
    time.sleep(random_number) 
    return num 

class MainWindow(QMainWindow): 
    def __init__(self): 
     QMainWindow.__init__(self) 
     self.toolBar = self.addToolBar("Toolbar") 
     self.toolBar.addAction(QAction('Add Task', self, triggered=self.addTask)) 
     self.list = QListWidget() 
     self.setCentralWidget(self.list) 

     # Pool of Background Processes 
     self.pool = multiprocessing.Pool(processes=4) 

    def addTask(self): 
     num_row = self.list.count() 
     self.pool.apply_async(func=compute, args=(num_row,), callback=self.receiveResult, 
           error_callback=self.receiveException) 
     item = QListWidgetItem("item %d" % num_row) 
     item.setForeground(Qt.gray) 
     self.list.addItem(item) 

    def receiveResult(self, result): 
     assert isinstance(result, int) 
     print("end_work(), where result is %s" % result) 
     self.list.item(result).setForeground(Qt.darkGreen) 

    def receiveException(self, exception): 
     error = str(exception) 
     _pos = error.find('_') + 1 
     num_row = int(error[_pos:]) 
     item = self.list.item(num_row) 
     item.setForeground(Qt.darkRed) 
     item.setText(item.text() + ' Retry...') 
     self.pool.apply_async(func=compute, args=(num_row,), callback=self.receiveResult, 
           error_callback=self.receiveException) 

if __name__ == '__main__': 
    app = QApplication(sys.argv) 
    main_window = MainWindow() 
    main_window.show() 
    sys.exit(app.exec_()) 

Edit: ho fatto un altro esempio utilizzando un QTimer invece di callback, controllando periodicamente la presenza di voci in una coda, l'aggiornamento di un QProgressBar:

#!/usr/bin/env python3 
import multiprocessing, multiprocessing.pool, time, random, sys 
from PySide.QtCore import * 
from PySide.QtGui import * 

def compute(num_row): 
    print("worker started at %d" % num_row) 
    random_number = random.randint(1, 10) 
    for second in range(random_number): 
     progress = float(second)/float(random_number) * 100 
     compute.queue.put((num_row, progress,)) 
     time.sleep(1) 
    compute.queue.put((num_row, 100)) 

def pool_init(queue): 
    # see http://stackoverflow.com/a/3843313/852994 
    compute.queue = queue 

class MainWindow(QMainWindow): 
    def __init__(self): 
     QMainWindow.__init__(self) 
     self.toolBar = self.addToolBar("Toolbar") 
     self.toolBar.addAction(QAction('Add Task', self, triggered=self.addTask)) 
     self.table = QTableWidget() 
     self.table.verticalHeader().hide() 
     self.table.setColumnCount(2) 
     self.setCentralWidget(self.table) 

     # Pool of Background Processes 
     self.queue = multiprocessing.Queue() 
     self.pool = multiprocessing.Pool(processes=4, initializer=pool_init, initargs=(self.queue,)) 

     # Check for progress periodically 
     self.timer = QTimer() 
     self.timer.timeout.connect(self.updateProgress) 
     self.timer.start(2000) 

    def addTask(self): 
     num_row = self.table.rowCount() 
     self.pool.apply_async(func=compute, args=(num_row,)) 
     label = QLabel("Queued") 
     bar = QProgressBar() 
     bar.setValue(0) 
     self.table.setRowCount(num_row + 1) 
     self.table.setCellWidget(num_row, 0, label) 
     self.table.setCellWidget(num_row, 1, bar) 

    def updateProgress(self): 
     if self.queue.empty(): return 
     num_row, progress = self.queue.get() # unpack 
     print("received progress of %s at %s" % (progress, num_row)) 
     label = self.table.cellWidget(num_row, 0) 
     bar = self.table.cellWidget(num_row, 1) 
     bar.setValue(progress) 
     if progress == 100: 
      label.setText('Finished') 
     elif label.text() == 'Queued': 
      label.setText('Downloading') 
     self.updateProgress() # recursion 

if __name__ == '__main__': 
    app = QApplication(sys.argv) 
    main_window = MainWindow() 
    main_window.show() 
    sys.exit(app.exec_()) 
+0

Grazie a @valmynd questo è estremamente utile per molti nella comunità di cui sono sicuro. Tuttavia, ho notato che la funzione 'apply_async' qui usa il valore di ritorno del metodo dato per il' callback'. In altre parole, viene chiamato 'callback = self.recieveResult' e viene dato il risultato di' compute' come parametro. Come renderebbe possibile questa configurazione per ottenere i dati dal processo in qualsiasi punto di un ciclo, o all'interno di una funzione che richiama 'compute', ecc.? – chase

+1

@chase Il modo più semplice che ho trovato per usare i callback è quello di passare la funzione come argomento al tuo 'compute'. In questo modo può chiamare 'recieveResult' tutte le volte che vuole. L'unico problema è che questo non è thread-safe e potrebbe rappresentare un problema se la funzione del gestore fa qualcosa di complicato. Un modo per ovviare a questo, ovviamente, è creare una coda di risultati su cui scrive il tuo 'fxn di calcolo su e da cui legge la tua funzione di gestore. Se il gestore è nella sua stessa discussione, mi sento come se funzionasse. – DaveTheScientist

+1

@chase Ho modificato la mia risposta e aggiunto un altro esempio senza callback, che indica le tue esigenze e può essere considerato più sicuro. Il primo esempio presentava un grave difetto: l'aggiornamento di una barra QProgress all'interno del metodo di callback non era possibile, poiché sembrava funzionare al di fuori del thread della GUI ([here] (http://stackoverflow.com/q/12138954/852994) qualcuno aveva problemi simili). – valmynd

2

Ok, non ho familiarità con Qt stesso, ma ho fatto un lavoro simile con Tkinter. Sono abbastanza sicuro che stai incontrando il Global Interpreter Lock di Python qui. Nello specifico, stai iniziando la tua coda e la tua applicazione GUI nello stesso thread, quindi quando la coda blocca l'attesa di input, anche la GUI sta bloccando. Prova ad avviare app = QtGui.QApplication([]) nella propria discussione o processo. Scrivere le GUI con le code è sempre un po 'complicato, e ho scoperto che di solito richiede almeno un ulteriore strato di threading di quanto non mi aspetti di aspettarmelo.

+0

Grazie @DaveTheScientist, ho sicuramente sentito questa spiegazione GIL per i miei problemi prima, ma non lo faccio Capisco perfettamente come sto ancora facendo la coda nello stesso thread della GUI quando la chiamo in una classe separata 'Worker_Thread'. Non dovrebbe passare tutti i messaggi dal processo al thread che viene creato lungo una scheda e quindi passare tali messaggi al thread della GUI principale nella mia configurazione? Questo era almeno il mio obiettivo per il setup che ho dato. In caso contrario, tuttavia, avvio la GUI in un thread separato se non è così? – chase

+1

Ok @chase, penso di vedere come hai impostato tutto questo. La mia domanda ora è, nella tua 'MainWindow class', quando viene restituito' tab.start_signal_emit() '? È possibile che stia bloccando fino al termine di ogni scheda? In questo caso, avviarebbe i processi delle schede e chiamerebbero 'self.step.setText (0, str (iterazione))' e 'print' come previsto, ma quelle chiamate' setText' sarebbero aggiunto alla coda interna della tua GUI, e in realtà non viene eseguito fino a quando tutte le chiamate 'tab.start_signal_emit()' ritornano. – DaveTheScientist

+1

Se questo è il problema, ti consiglio di iniziare un nuovo thread (o processo da 'mp') per ogni iterazione in quel ciclo for in 'MainWindow'. Un'altra cosa da provare potrebbe essere in 'Worker_Thread.start_computation()', quando chiama 'self.queue.get()', aggiunge un valore di timeout: 'self.queue.get (True, 1.0)'. – DaveTheScientist

3

Tutto ciò che avete fatto in Worker_Thread avrebbe dovuto essere spostato sul lavoratore. Qt assegna l'affinità del thread a ciascuno degli oggetti in base a dove viene creato l'oggetto. Gli oggetti Worker_Thread sono creati nel thread principale, quindi ha affinità thread principale. Se un segnale dal thread principale è collegato a uno slot di un oggetto creato nel thread principale, lo slot verrà eseguito anche nel thread principale. (Non importa che sia QueuedConnection o DirectConnection). E lo slot blocca la GUI.

Fate questo:

class Worker: 
    update_signal = QtCore.pyqtSignal(int) 
    done_signal = QtCore.pyqtSignal() 

    def __init__(self, some_var): 
     self.some_var = some_var 
     self.iteration = 0 
     self.queue = mp.Queue() 
     self.process = mp.Process(target=self.some_complex_processing, args=(self.queue,)) 

    def some_complex_processing(self, queue): 
     for i in range(0,5000): 
      self.iteration += 1 
      queue.put(self.iteration) 
     queue.put('done with processing') 

    @QtCore.pyqtSlot() 
    def start_computation(self): 
     self.process.start() 
     while(True): 
      try: 
       message = self.queue.get() 
       self.update_signal.emit(message) 
      except EOFError: 
       pass 
      if message == 'done with processing': 
       self.done_signal.emit() 
       break 
     self.process.join() 
     return 



class Tab(QtGui.QTabWidget): 
    start_comp = QtCore.pyqtSignal() 
    def __init__(self, parent, this_worker): 
     self.parent = parent 
     self.this_worker = this_worker 
     QtGui.QTabWidget.__init__(self, parent) 

     self.treeWidget = QtGui.QTreeWidget(self) 
     self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"]) 
     self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"]) 

     # Use QThread is enough 
     self.thread = QtCore.QThread(); 
     # Change the thread affinity of worker to self.thread. 
     self.this_worker.moveToThread(self.thread); 
     self.this_worker.update_signal.connect(self.update_GUI) 
     self.this_worker.done_signal.connect(self.thread.quit) 
     self.start_comp.connect(self.this_worker.start_computation) 
     self.thread.start() 

    ############################### 
    # Here is what should update the GUI at every iteration of Worker.some_complex_processing() 
    # The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated. 
    @QtCore.pyqtSlot(int) 
    def update_GUI(self, iteration): 
     self.step.setText(0, str(iteration)) 
     #time.sleep(0.1) 
     print iteration 

    def start_signal_emit(self): 
     self.start_comp.emit() 
+0

Grazie mille per l'aiuto. Questo ha sicuramente raggiunto un punto cruciale del problema, ma ora non riesco a far uscire i messaggi dal processo per aggiornare la GUI per qualche motivo. Carico una modifica nella mia risposta e continuo a lavorarci per vedere se tu o io abbiamo altre idee. – chase

+0

avete qualche idea del perché i campi di testo non sono impostati nella GUI usando questo codice? – chase

4

Grazie mille per aver postato questa domanda e tutti i contributori per il loro contributo. Mi ha fornito un'impalcatura utile per sperimentare con PyQt e multiprocessing.

Ho iniziato con il secondo esempio di codice elencato nella domanda. Le mie modifiche e commenti:

  • In Windows, tutti argomenti Process.__init__() devono essere pickleable. Vedrai che @valmynd rende la sua funzione compute una funzione di primo livello, proprio per questo motivo. Ciò è in parte dovuto al fatto che il multiprocessing reimporterà il modulo contenente la funzione target. Per ricordarmelo, provo a mettere la funzione obiettivo nel suo modulo (e assicurarmi che tutte le informazioni siano passate come argomenti selezionabili). Ho spostato la complessa funzione di elaborazione nel suo modulo chiamato workermodule.py.
  • Senza un lavoro sufficiente nella complessa funzione di elaborazione, il ciclo termina troppo rapidamente per consentire a qualsiasi modifica di essere visibile nella GUI.Pertanto, ho aggiunto del lavoro extra (inutile) all'interno della complessa funzione di elaborazione. Come notato nel commento, è possibile time.sleep, ma è più soddisfacente accendere tutti i core per un po '.
  • Con i seguenti due frammenti di codice, ottengo un'interfaccia grafica liscia con aggiornamenti continui al valore di iterazione e i processi secondari vengono eseguiti alla massima velocità.
  • Si noti che è possibile creare self.process con due code come argomenti, uno per l'input e uno per l'output. Quindi la complessa funzione di elaborazione dovrebbe controllare periodicamente la coda di input per i dati.

workermodule.py:

import time 

def some_complex_processing(queue): 
    iteration = 0 
    for i in range(0,5000): 
     iteration += 1 
     queue.put(iteration) 
     #time.sleep(20e-3) # ms 
     # You could time.sleep here to simulate a 
     # long-running process, but just to prove 
     # that we're not cheating, let's make this 
     # process work hard, while the GUI process 
     # should still have smooth drawing. 
     for x in range(100000): 
      y = x 
    queue.put('done with processing') 

mainfile.py:

from PyQt4 import QtCore, QtGui 
import multiprocessing as mp 
import sys 
import workermodule 

class Worker(QtCore.QObject): 
    update_signal = QtCore.pyqtSignal(int) 
    done_signal = QtCore.pyqtSignal() 

    def __init__(self, some_var): 
     QtCore.QObject.__init__(self, parent=None) 
     self.some_var = some_var 
     self.queue = mp.Queue() 
     self.process = mp.Process(
      target=workermodule.some_complex_processing, 
      args=(self.queue,) 
      ) 

    @QtCore.pyqtSlot() 
    def start_computation(self): 
     self.process.start() 
     while True: 
      try: 
       message = self.queue.get() 
       self.update_signal.emit(message) 
      except EOFError: 
       pass 
      if message == 'done with processing': 
       self.done_signal.emit() 
       break 
     self.process.join() 
     return 

class Tab(QtGui.QTabWidget): 
    start_comp = QtCore.pyqtSignal() 
    def __init__(self, parent, this_worker): 
     self.parent = parent 
     self.this_worker = this_worker 
     QtGui.QTabWidget.__init__(self, parent) 

     self.treeWidget = QtGui.QTreeWidget(self) 
     self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"]) 
     self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"]) 

     # Use QThread is enough 
     self.thread = QtCore.QThread(); 
     # Change the thread affinity of worker to self.thread. 
     self.this_worker.moveToThread(self.thread); 
     self.this_worker.update_signal.connect(self.update_GUI) 
     self.this_worker.done_signal.connect(self.thread.quit) 
     self.start_comp.connect(self.this_worker.start_computation) 
     self.thread.start() 

    ############################### 
    # Here is what should update the GUI at every iteration of Worker.some_complex_processing() 
    # The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated. 
    @QtCore.pyqtSlot(int) 
    def update_GUI(self, iteration): 
     self.step.setText(0, str(iteration)) 
     print iteration 

    def start_signal_emit(self): 
     self.start_comp.emit() 

# GUI stuff 
class MainWindow(QtGui.QMainWindow): 
    def __init__(self, parent = None): 
     QtGui.QMainWindow.__init__(self) 
     self.tab_list = [] 
     self.setTabShape(QtGui.QTabWidget.Rounded) 
     self.centralwidget = QtGui.QWidget(self) 
     self.top_level_layout = QtGui.QGridLayout(self.centralwidget) 

     self.tabWidget = QtGui.QTabWidget(self.centralwidget) 
     self.top_level_layout.addWidget(self.tabWidget, 1, 0, 25, 25) 

     process_button = QtGui.QPushButton("Process") 
     self.top_level_layout.addWidget(process_button, 0, 1) 
     QtCore.QObject.connect(process_button, QtCore.SIGNAL("clicked()"), self.process) 

     self.setCentralWidget(self.centralwidget) 
     self.centralwidget.setLayout(self.top_level_layout) 

     # Make Tabs in loop from button 
     for i in range(0,10): 
      name = 'tab' + str(i) 
      self.tab_list.append(Tab(self.tabWidget, Worker(name))) 
      self.tabWidget.addTab(self.tab_list[-1], name) 

    # Do the processing 
    def process(self): 
     for tab in self.tab_list: 
      tab.start_signal_emit() 
     return 

if __name__ == "__main__": 
    app = QtGui.QApplication([]) 
    win = MainWindow() 
    win.show() 
    sys.exit(app.exec_()) 
+0

È stata sostituita una funzione getter con una proprietà due volte, vale a dire: 'self.parent = parent',' self.centralWidget = QtGui.QWidget (self) '. Questa non è una buona pratica. – MadeOfAir

Problemi correlati