2013-06-02 22 views
23

Próbuję nauczyć się używać QThreads w aplikacji PyQt Gui. Mam rzeczy, które działają przez jakiś czas, z (zwykle) punktami, w których mogłem zaktualizować Gui, ale chciałbym podzielić główne prace na swój własny wątek (czasem coś się zacina, i byłoby miło mieć w końcu anuluj/spróbuj ponownie, co oczywiście nie działa, jeśli Gui jest zamrożone, ponieważ główna pętla jest zablokowana).Przykład prawidłowego użycia QThread w PyQt?

Przeczytałem https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/. Ta strona mówi, że ponowne wdrożenie metody run nie jest sposobem na zrobienie tego. Problem, który mam, polega na znalezieniu przykładu PyQt, który ma główny wątek robiący Gui i wątek roboczy, który nie robi tego w ten sposób. Wpis na blogu dotyczy języka C++, więc chociaż przykłady pomagają, wciąż jestem trochę zagubiony. Czy ktoś może wskazać mi przykład prawidłowego sposobu na to w Pythonie?

+4

To wygląda DUP z [wątku tła z QThread w PyQt] (http://stackoverflow.com/questions/6783194/background-thread-with-qthread-in-pyqt). Drugi przykład zaakceptowanej odpowiedzi wygląda na proste tłumaczenie kodu C++ z bloga, który podłączyłeś. – abarnert

+0

Czy napisałeś również natywny kod wątków Pythona (z 'threading.Thread' itp.)? Jeśli nie, możesz najpierw popracować nad przykładami. (Zobacz także [Wątek w aplikacji PyQt: użyj wątków Qt lub wątków Pythona] (http://stackoverflow.com/questions/1595649/threading-in-a-pyqt-application-use-qt-threads-or-python- wątki), aby sprawdzić, czy potrzebujesz jeszcze QThread.) – abarnert

+0

@abarnert Dzięki, myślę, że ten link był właśnie tym, czego szukałem. Widziałem drugi link i zdecydowałem, że powinienem używać QThreads, ponieważ chciałem móc wysyłać szczeliny/sygnały między wątkami. Wiedziałem, że 'threading.Thread' istnieje, ale wcześniej go nie użyłem. Zrobiłem dużo wyszukiwania, a nawet zobaczyłem pierwszy link, przejrzałem go, zobaczyłem 'def run' i ruszyłem dalej, nie zdając sobie sprawy z pokazanych w obu kierunkach! – Azendale

Odpowiedz

7

Oto działający przykład oddzielnego wątku roboczego, który może wysyłać i odbierać sygnały, aby umożliwić komunikację z GUI.

Wykonałem dwa proste przyciski, jeden, który rozpoczyna długie obliczenia w osobnym wątku, i jeden, który natychmiast kończy obliczenia i resetuje wątek roboczy.

Przymusowe kończenie wątku, jak to się tutaj robi, nie jest na ogół najlepszym sposobem robienia rzeczy, ale są sytuacje, w których zawsze urocze wyjście nie jest opcją.

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

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

     # Make any cross object connections. 
     self._connectSignals() 

     self.gui.show() 

    def _connectSignals(self): 
     self.gui.button_start.clicked.connect(self.worker.startWork) 
     self.gui.button_cancel.clicked.connect(self.forceWorkerReset) 
     self.signalStatus.connect(self.gui.updateStatus) 
     self.worker.signalStatus.connect(self.gui.updateStatus) 

     self.parent().aboutToQuit.connect(self.forceWorkerQuit) 

    def forceWorkerReset(self): 
     if self.worker_thread.isRunning(): 
      self.worker_thread.terminate() 
      self.worker_thread.wait() 

      self.signalStatus.emit('Idle.') 
      self.worker_thread.start() 

    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

Kiedy to uruchomię, otrzymuję. 'Qt wychwycił wyjątek wyrzucony z programu obsługi zdarzeń. Zgłaszanie wyjątków z obsługi zdarzeń nie jest obsługiwane w Qt. Musisz ponownie zaimplementować QApplication :: notify() i wychwycić wszystkie wyjątki. " –

+0

Czy wiesz, jaki był wyjątek? Przetestowałem ten fragment przy użyciu PyQt4 z Pythonem 2.7 na OS X bez żadnych błędów. Kiedy próbuję użyć tego w Pythonie 3.4, czasami dostaję błędy segmentacji. Nie jestem pewien, czy ten problem jest związany z kodem w formie pisemnej, czy z konkretną (zależną od wersji) implementacją. – amicitas

+0

Nie badałem, co jest rzucaniem, ale najwyraźniej masz coś, co czasami rzuca wyjątek w pętlę wiadomości, która nigdy nie może się zdarzyć. –

0

Masz rację, że dobrze jest mieć wątek roboczy podczas przetwarzania, podczas gdy główny wątek wykonuje GUI. Ponadto PyQt zapewnia oprzyrządowanie gwintowe z mechanizmem sygnału/szczeliny, który jest bezpieczny dla wątków.

This may sound of interest. W swoim przykładzie, budują 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) 

(simpel ui zawierające widżet listy, które dodamy kilka elementów do klikając przycisk)

Następnie można stworzyć własną klasę gwintu, jeden exemple jest

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

Na nowo definiuje się metodę run(). Możesz znaleźć alternatywę dla terminate(), zobacz samouczek.

+9

Program operacyjny wyraźnie powiedział, że chce używać mechanizmu 'moveToThread' zamiast mechanizmu' QThread.run'. Nie jestem pewien, czy ma ku temu dobry powód, ale wciąż nie odpowiadasz na jego pytanie. – abarnert

+1

OP ma rację: nie powinieneś podklasować QThread. Zobacz http: //blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/ – blokeley

+0

[Dokumentacja Qt na temat gwintowania] (http://doc.qt.io/qt-4.8/thread-basics.html#gui-thread i wątek roboczy) wspominają podklasę QThread choć ... –

Powiązane problemy