簡體   English   中英

在PyQt中正確使用QThread的例子?

[英]Example of the right way to use QThread in PyQt?

我正在嘗試學習如何在 PyQt Gui 應用程序中使用 QThreads。 我有一些東西可以運行一段時間,(通常)有一些我可以更新 Gui 的點,但我想將主要工作拆分到它自己的線程中(有時東西會卡住,如果最終有一個取消/重試按鈕,如果 Gui 由於主循環被阻塞而被凍結,這顯然不起作用)。

我讀過https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/ 該頁面說重新實現run方法不是這樣做的方法。 我遇到的問題是找到一個 PyQt 示例,它有一個執行 Gui 的主線程和一個不這樣做的工作線程。 這篇博文是針對 C++ 的,所以雖然它的例子確實有幫助,但我還是有點迷茫。 有人可以在 Python 中指出正確方法的示例嗎?

下面是一個單獨的工作線程的工作示例,它可以發送和接收信號以允許它與GUI通信。

我做了兩個簡單的按鈕,一個在一個單獨的線程中開始一個長計算,一個立即終止計算並重置工作線程。

如此處強制終止線程通常不是最好的做事方式,但有些情況下,總是優雅地退出不是一種選擇。

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

你是對的,在主線程正在執行GUI時讓工作線程進行處理是一件好事。 此外,PyQt為線程檢測提供了一個線程安全的信號/槽機制。

這可能聽起來很有趣 在他們的示例中,他們構建了一個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)

(簡單的ui包含一個列表小部件,我們將通過單擊按鈕添加一些項目)

然后,您可以創建我們自己的線程類,例如

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

您確實重新定義了run()方法。 您可以找到terminate()的替代方法,請參閱教程。

在我看來,到目前為止,最好的解釋是在此處找到示例代碼,示例代碼最初沒有響應,然后得到改進。

請注意,這確實使用了所需的(非子類) QThreadmoveToThread方法,文章聲稱這是首選方法。

上面的鏈接頁面還提供了 PyQt5 等同於 C Qt 頁面,給出了Maya Posch 從 2011 年開始的明確解釋。我認為她當時可能正在使用 Qt4,但該頁面仍然適用於 Qt5(因此 PyQt5)並且非常值得深入研究,包括許多評論(和她的回復)。

以防有一天上面的第一個鏈接變為 404(這會很糟糕):這是基本的 Python 代碼,它等同於 Maya 的 C 代碼:

self.thread = QtCore.QThread()
# Step 3: Create a worker object
self.worker = Worker()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
# Step 5: Connect signals and slots
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.reportProgress)
# Step 6: Start the thread
self.thread.start()

# Final resets
self.longRunningBtn.setEnabled(False)
self.thread.finished.connect(
    lambda: self.longRunningBtn.setEnabled(True)
)
self.thread.finished.connect(
    lambda: self.stepLabel.setText("Long-Running Step: 0")
)   

該頁面示例中的 NB selfQMainWindow object。我認為您可能必須非常小心將QThread實例作為屬性附加到什么:實例在 scope 中的 go 時被銷毀,但具有QThread屬性,或者實際上是一個從 scope 出來的本地QThread實例,似乎能夠導致一些莫名其妙的 Python 崩潰,這些崩潰沒有被sys.excepthook (或sys.unraisablehook )發現。 建議謹慎。

... Worker看起來像這樣:

class Worker(QtCore.QObject):
    finished = QtCore.pyqtSignal()
    progress = QtCore.pyqtSignal(int)

    def run(self):
        """Long-running task."""
        for i in range(5):
            sleep(1)
            self.progress.emit(i + 1)
        self.finished.emit()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM