简体   繁体   English

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

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

I'm trying to learn how to use QThreads in a PyQt Gui application.我正在尝试学习如何在 PyQt Gui 应用程序中使用 QThreads。 I have stuff that runs for a while, with (usually) points where I could update a Gui, but I would like to split the main work out to its own thread (sometimes stuff gets stuck, and it would be nice to eventually have a cancel/try again button, which obviously doesn't work if the Gui is frozen because the Main Loop is blocked).我有一些东西可以运行一段时间,(通常)有一些我可以更新 Gui 的点,但我想将主要工作拆分到它自己的线程中(有时东西会卡住,如果最终有一个取消/重试按钮,如果 Gui 由于主循环被阻塞而被冻结,这显然不起作用)。

I've read https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/ .我读过https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/ That page says that re-implementing the run method is not the way to do it.该页面说重新实现run方法不是这样做的方法。 The problem I am having is finding a PyQt example that has a main thread doing the Gui and a worker thread that does not do it that way.我遇到的问题是找到一个 PyQt 示例,它有一个执行 Gui 的主线程和一个不这样做的工作线程。 The blog post is for C++, so while it's examples do help, I'm still a little lost.这篇博文是针对 C++ 的,所以虽然它的例子确实有帮助,但我还是有点迷茫。 Can someone please point me to an example of the right way to do it in Python?有人可以在 Python 中指出正确方法的示例吗?

Here is a working example of a separate worker thread which can send and receive signals to allow it to communicate with a GUI. 下面是一个单独的工作线程的工作示例,它可以发送和接收信号以允许它与GUI通信。

I made two simple buttons, one which starts a long calculation in a separate thread, and one which immediately terminates the calculation and resets the worker thread. 我做了两个简单的按钮,一个在一个单独的线程中开始一个长计算,一个立即终止计算并重置工作线程。

Forcibly terminating a thread as is done here is not generally the best way to do things, but there are situations in which always gracefully exiting is not an 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_())

You are right that it is a good thing to have a worker thread doing the processing while main thread is doing the GUI. 你是对的,在主线程正在执行GUI时让工作线程进行处理是一件好事。 Also, PyQt is providing thread instrumentation with a signal/slot mechanism that is thread safe. 此外,PyQt为线程检测提供了一个线程安全的信号/槽机制。

This may sound of interest . 这可能听起来很有趣 In their example, they build a GUI 在他们的示例中,他们构建了一个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)

(simple ui containing a list widget which we will add some items to by clicking a button) (简单的ui包含一个列表小部件,我们将通过单击按钮添加一些项目)

You may then create our own thread class, one example is 然后,您可以创建我们自己的线程类,例如

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

You do redefine the run() method. 您确实重新定义了run()方法。 You may find an alternative to terminate() , see the tutorial. 您可以找到terminate()的替代方法,请参阅教程。

In my opinion, by far the best explanation, with example code which is initially unresponsive, and is then improved, is to be found here .在我看来,到目前为止,最好的解释是在此处找到示例代码,示例代码最初没有响应,然后得到改进。

Note that this does indeed use the desired (non-subclassed) QThread and moveToThread approach, which the article claims to be the preferred approach.请注意,这确实使用了所需的(非子类) QThreadmoveToThread方法,文章声称这是首选方法。

The above linked page also provides the PyQt5 equivalent to the C Qt page giving the definitive explanation by Maya Posch from 2011. I think she was probably using Qt4 at the time, but that page is still applicable in Qt5 (hence PyQt5) and well worth studying in depth, including many of the comments (and her replies).上面的链接页面还提供了 PyQt5 等同于 C Qt 页面,给出了Maya Posch 从 2011 年开始的明确解释。我认为她当时可能正在使用 Qt4,但该页面仍然适用于 Qt5(因此 PyQt5)并且非常值得深入研究,包括许多评论(和她的回复)。

Just in case the first link above one day goes 404 (which would be terrible,): this is the essential Python code which is equivalent to Maya's C code:以防有一天上面的第一个链接变为 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 self in the example on that page is the QMainWindow object. I think you may have to be quite careful about what you attach QThread instances to as properties: instances which are destroyed when they go out of scope, but which have a QThread property, or indeed a local QThread instance which goes out of scope, seem to be capable of causing some inexplicable Python crashes, which aren't picked up by sys.excepthook (or the sys.unraisablehook ).该页面示例中的 NB selfQMainWindow object。我认为您可能必须非常小心将QThread实例作为属性附加到什么:实例在 scope 中的 go 时被销毁,但具有QThread属性,或者实际上是一个从 scope 出来的本地QThread实例,似乎能够导致一些莫名其妙的 Python 崩溃,这些崩溃没有被sys.excepthook (或sys.unraisablehook )发现。 Caution advised.建议谨慎。

... where Worker looks something like this: ... 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