PyQt中带有QThread的后台线程

Posted

技术标签:

【中文标题】PyQt中带有QThread的后台线程【英文标题】:Background thread with QThread in PyQt 【发布时间】:2011-10-10 14:58:17 【问题描述】:

我有一个程序,它通过我在 PyQt 中编写的 gui 与我正在使用的收音机接口。显然无线电的主要功能之一是传输数据,但要连续执行此操作,我必须循环写入,这会导致 gui 挂起。由于我从未处理过线程,因此我尝试使用QCoreApplication.processEvents(). 摆脱这些挂起,但是无线电需要在传输之间休眠,因此 gui 仍然根据这些休眠持续的时间而挂起。

有没有使用 QThread 解决此问题的简单方法?我一直在寻找有关如何使用 PyQt 实现多线程的教程,但其中大多数都涉及设置服务器,并且比我需要的要先进得多。老实说,我什至不需要我的线程在运行时更新任何内容,我只需要启动它,让它在后台传输,然后停止它。

【问题讨论】:

【参考方案1】:

我创建了一个小示例,展示了处理线程的 3 种不同且简单的方法。我希望它能帮助您找到解决问题的正确方法。

import sys
import time

from PyQt5.QtCore import (QCoreApplication, QObject, QRunnable, QThread,
                          QThreadPool, pyqtSignal)


# Subclassing QThread
# http://qt-project.org/doc/latest/qthread.html
class AThread(QThread):

    def run(self):
        count = 0
        while count < 5:
            time.sleep(1)
            print("A Increasing")
            count += 1

# Subclassing QObject and using moveToThread
# http://blog.qt.digia.com/blog/2007/07/05/qthreads-no-longer-abstract
class SomeObject(QObject):

    finished = pyqtSignal()

    def long_running(self):
        count = 0
        while count < 5:
            time.sleep(1)
            print("B Increasing")
            count += 1
        self.finished.emit()

# Using a QRunnable
# http://qt-project.org/doc/latest/qthreadpool.html
# Note that a QRunnable isn't a subclass of QObject and therefore does
# not provide signals and slots.
class Runnable(QRunnable):

    def run(self):
        count = 0
        app = QCoreApplication.instance()
        while count < 5:
            print("C Increasing")
            time.sleep(1)
            count += 1
        app.quit()


def using_q_thread():
    app = QCoreApplication([])
    thread = AThread()
    thread.finished.connect(app.exit)
    thread.start()
    sys.exit(app.exec_())

def using_move_to_thread():
    app = QCoreApplication([])
    objThread = QThread()
    obj = SomeObject()
    obj.moveToThread(objThread)
    obj.finished.connect(objThread.quit)
    objThread.started.connect(obj.long_running)
    objThread.finished.connect(app.exit)
    objThread.start()
    sys.exit(app.exec_())

def using_q_runnable():
    app = QCoreApplication([])
    runnable = Runnable()
    QThreadPool.globalInstance().start(runnable)
    sys.exit(app.exec_())

if __name__ == "__main__":
    #using_q_thread()
    #using_move_to_thread()
    using_q_runnable()

【讨论】:

是的,您可以添加自己的信号。一种方法是使用更新后的值发出信号(例如 pyqtSignal(int)),并从您的 GUI 类连接到它以相应地更新 QSpinBox。 我发现了一种用 PyQt 4.6 修复它的奇怪方法。似乎没有正确调用 QThread::run() (我的猜测是这与 QThread::run() 不再是纯虚函数有关)。这听起来很愚蠢,但要解决它,只需创建自己的 QThread 子类,重新实现 run() 并填写 QThread.run(self)。就是这样,它神奇地起作用了 不要继承QThread这样做总是是错误的,因为QThread对象存在于父线程中;因此,在此对象上发出信号的槽在父线程中运行,而不是在 QThread 对象封装的实际线程中运行。信号槽连接是 Qt 中线程同步的全部基础。因此,子类化QThread 阻碍了同步。另请参阅:How To Really, Truly Use QThreads; The Full Explanation, 关于该主题的开创性文章。 你推荐哪一种用于一般用途? @CecilCurry woboq.com/blog/qthread-you-were-not-doing-so-wrong.html 如果我做对了,如果您不在新类中使用插槽,则可以继承 QThread 【参考方案2】:

将此答案更新为 PyQt5, python 3.4

将此作为一种模式来启动一个不获取数据并在表单可用时返回数据的工作器。

1 - Worker 类变得更小,并放在自己的文件 worker.py 中,以便于记忆和独立软件重用。

2 - main.py 文件是定义 GUI Form 类的文件

3 - 线程对象没有子类化。

4 - 线程对象和worker对象都属于Form对象

5 - 程序的步骤在 cmets 内。

# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time


class Worker(QObject):
    finished = pyqtSignal()
    intReady = pyqtSignal(int)


    @pyqtSlot()
    def procCounter(self): # A slot takes no params
        for i in range(1, 100):
            time.sleep(1)
            self.intReady.emit(i)

        self.finished.emit()

主要文件是:

  # main.py
  from PyQt5.QtCore import QThread
  from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
  import sys
  import worker


  class Form(QWidget):

    def __init__(self):
       super().__init__()
       self.label = QLabel("0")

       # 1 - create Worker and Thread inside the Form
       self.obj = worker.Worker()  # no parent!
       self.thread = QThread()  # no parent!

       # 2 - Connect Worker`s Signals to Form method slots to post data.
       self.obj.intReady.connect(self.onIntReady)

       # 3 - Move the Worker object to the Thread object
       self.obj.moveToThread(self.thread)

       # 4 - Connect Worker Signals to the Thread slots
       self.obj.finished.connect(self.thread.quit)

       # 5 - Connect Thread started signal to Worker operational slot method
       self.thread.started.connect(self.obj.procCounter)

       # * - Thread finished signal will close the app if you want!
       #self.thread.finished.connect(app.exit)

       # 6 - Start the thread
       self.thread.start()

       # 7 - Start the form
       self.initUI()


    def initUI(self):
        grid = QGridLayout()
        self.setLayout(grid)
        grid.addWidget(self.label,0,0)

        self.move(300, 150)
        self.setWindowTitle('thread test')
        self.show()

    def onIntReady(self, i):
        self.label.setText("".format(i))
        #print(i)

    app = QApplication(sys.argv)

    form = Form()

    sys.exit(app.exec_())

【讨论】:

对于那些不知道的人,请参阅this 问题,了解为什么使用 pyqtSlot()` 装饰器和此答案中概述的信号连接的特定顺序很重要。 你真是天赐之物,谢谢,谢谢,谢谢强调了 Worker 和 Thread 需要没有父级的事实!在过去的三个小时里,我一直在与QThread: Destroyed while thread is still running 打交道,然后我读到了这篇文章,它点击了! 老兄......真正的上帝 为什么不把父对象传递给工作对象和这里的线程呢?那就是我做错了,因为那是 C++ 方式!我找不到任何解释为什么你不在 python 中这样做。 当我希望 onIntReady 函数位于另一个模块中并希望将输入传递给它时,如何使用此解决方案?【参考方案3】:

来自 Matt 的非常好的示例,我修正了错字,而且 pyqt4.8 现在很常见,所以我也删除了虚拟类并为 dataReady 信号添加了一个示例

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import Qt


# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
    finished = QtCore.pyqtSignal()
    dataReady = QtCore.pyqtSignal(list, dict)

    @QtCore.pyqtSlot()
    def processA(self):
        print "Worker.processA()"
        self.finished.emit()

    @QtCore.pyqtSlot(str, list, list)
    def processB(self, foo, bar=None, baz=None):
        print "Worker.processB()"
        for thing in bar:
            # lots of processing...
            self.dataReady.emit(['dummy', 'data'], 'dummy': ['data'])
        self.finished.emit()


def onDataReady(aList, aDict):
    print 'onDataReady'
    print repr(aList)
    print repr(aDict)


app = QtGui.QApplication(sys.argv)

thread = QtCore.QThread()  # no parent!
obj = Worker()  # no parent!
obj.dataReady.connect(onDataReady)

obj.moveToThread(thread)

# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)

# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread.  As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.finished.connect(app.exit)

thread.start()

# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
                                QtCore.Q_ARG(str, "Hello World!"),
                                QtCore.Q_ARG(list, ["args", 0, 1]),
                                QtCore.Q_ARG(list, []))

# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls.  Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking

app.exec_()

【讨论】:

非常好。这应该是当今公认的答案! QMetaObject 丑得要命 是的,QMetaObject 很丑陋,但在底层,这就是 Qt 的信号和插槽所使用的。这允许调用者以线程安全的方式有效地将消息添加到 Worker 的信号队列中。查看我对使用 QRunnables 的回答。它也可能有自己的缺陷,但它在你的 PyQt 应用程序中获得异步行为方面非常强大。它也不使用 QMetaObject 从 qthread 继承显然很好:woboq.com/blog/qthread-you-were-not-doing-so-wrong.html 不,这应该是公认的答案。当您可以简单地定义正确的信号槽连接时,绝对没有正当理由利用基于QMetaObject 的黑客攻击,其前缀是整个挥手评论墙。 &lt;/sigh&gt;【参考方案4】:

根据 Qt 开发人员的说法,子类化 QThread 是不正确的(请参阅http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/)。但是那篇文章真的很难理解(加上标题有点居高临下)。我发现了一篇更好的博客文章,它更详细地解释了为什么应该使用一种线程样式而不是另一种线程:http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

在我看来,您可能永远不应该为了重载 run 方法而对线程进行子类化。虽然这确实有效,但您基本上是在规避 Qt 希望您如何工作。另外,您会错过诸如事件和适当的线程安全信号和插槽之类的事情。另外,正如您可能会在上面的博客文章中看到的那样,“正确”的线程方式会迫使您编写更多可测试的代码。

这里有几个关于如何在 PyQt 中利用 QThreads 的示例(我在下面发布了一个单独的答案,该答案正确使用了 QRunnable 并结合了信号/插槽,如果您有很多需要执行的异步任务,该答案会更好负载平衡)。

import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt

# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
    finished = QtCore.pyqtSignal()
    dataReady = QtCore.pyqtSignal(list, dict)

    @QtCore.pyqtSlot()
    def processA(self):
        print "Worker.processA()"
        self.finished.emit()

    @QtCore.pyqtSlot(str, list, list)
    def processB(self, foo, bar=None, baz=None):
        print "Worker.processB()"
        for thing in bar:
            # lots of processing...
            self.dataReady.emit(['dummy', 'data'], 'dummy': ['data'])
        self.finished.emit()


class Thread(QtCore.QThread):
    """Need for PyQt4 <= 4.6 only"""
    def __init__(self, parent=None):
        QtCore.QThread.__init__(self, parent)

     # this class is solely needed for these two methods, there
     # appears to be a bug in PyQt 4.6 that requires you to
     # explicitly call run and start from the subclass in order
     # to get the thread to actually start an event loop

    def start(self):
        QtCore.QThread.start(self)

    def run(self):
        QtCore.QThread.run(self)


app = QtGui.QApplication(sys.argv)

thread = Thread() # no parent!
obj = Worker() # no parent!
obj.moveToThread(thread)

# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)

# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread.  As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.start()

# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
                                QtCore.Q_ARG(str, "Hello World!"),
                                QtCore.Q_ARG(list, ["args", 0, 1]),
                                QtCore.Q_ARG(list, []))

# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for 
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls.  Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking

app.exec_()

# Without this you may get weird QThread messages in the shell on exit
app.deleteLater()        

【讨论】:

来自另一个 Qt Dev woboq.com/blog/qthread-you-were-not-doing-so-wrong.html 子类化 QThread 仅在实现 run 方法时是完全可以接受的。 appears to be a bug in PyQt 4.6 你能指出这个问题,所以我们知道它是否/何时修复。 支持 Posch 文章的链接,因为它消除了冲突的观点。 从 qthread 继承显然没问题:woboq.com/blog/qthread-you-were-not-doing-so-wrong.html 您的链接已过时,您可能需要更新它:blog.qt.io/blog/2010/06/17/youre-doing-it-wrong【参考方案5】:

在 PyQt 中有很多获取异步行为的选项。对于需要事件处理的事情(即 QtNetwork 等),您应该使用我在此线程的其他答案中提供的 QThread 示例。但是对于您的绝大多数线程需求,我认为这种解决方案比其他方法要优越得多。

这样做的好处是 QThreadPool 将您的 QRunnable 实例安排为任务。这类似于英特尔 TBB 中使用的任务模式。它不像我喜欢的那样优雅,但它确实实现了出色的异步行为。

这允许您通过 QRunnable 在 Python 中利用 Qt 的大部分线程功能,并且仍然可以利用信号和插槽。我在几个应用程序中使用了相同的代码,一些进行数百次异步 REST 调用,一些打开文件或列出目录,最好的部分是使用这种方法,Qt 任务为我平衡了系统资源。

import time
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt


def async(method, args, uid, readycb, errorcb=None):
    """
    Asynchronously runs a task

    :param func method: the method to run in a thread
    :param object uid: a unique identifier for this task (used for verification)
    :param slot updatecb: the callback when data is receieved cb(uid, data)
    :param slot errorcb: the callback when there is an error cb(uid, errmsg)

    The uid option is useful when the calling code makes multiple async calls
    and the callbacks need some context about what was sent to the async method.
    For example, if you use this method to thread a long running database call
    and the user decides they want to cancel it and start a different one, the
    first one may complete before you have a chance to cancel the task.  In that
    case, the "readycb" will be called with the cancelled task's data.  The uid
    can be used to differentiate those two calls (ie. using the sql query).

    :returns: Request instance
    """
    request = Request(method, args, uid, readycb, errorcb)
    QtCore.QThreadPool.globalInstance().start(request)
    return request


class Request(QtCore.QRunnable):
    """
    A Qt object that represents an asynchronous task

    :param func method: the method to call
    :param list args: list of arguments to pass to method
    :param object uid: a unique identifier (used for verification)
    :param slot readycb: the callback used when data is receieved
    :param slot errorcb: the callback used when there is an error

    The uid param is sent to your error and update callbacks as the
    first argument. It's there to verify the data you're returning

    After created it should be used by invoking:

    .. code-block:: python

       task = Request(...)
       QtCore.QThreadPool.globalInstance().start(task)

    """
    INSTANCES = []
    FINISHED = []
    def __init__(self, method, args, uid, readycb, errorcb=None):
        super(Request, self).__init__()
        self.setAutoDelete(True)
        self.cancelled = False

        self.method = method
        self.args = args
        self.uid = uid
        self.dataReady = readycb
        self.dataError = errorcb

        Request.INSTANCES.append(self)

        # release all of the finished tasks
        Request.FINISHED = []

    def run(self):
        """
        Method automatically called by Qt when the runnable is ready to run.
        This will run in a separate thread.
        """
        # this allows us to "cancel" queued tasks if needed, should be done
        # on shutdown to prevent the app from hanging
        if self.cancelled:
            self.cleanup()
            return

        # runs in a separate thread, for proper async signal/slot behavior
        # the object that emits the signals must be created in this thread.
        # Its not possible to run grabber.moveToThread(QThread.currentThread())
        # so to get this QObject to properly exhibit asynchronous
        # signal and slot behavior it needs to live in the thread that
        # we're running in, creating the object from within this thread
        # is an easy way to do that.
        grabber = Requester()
        grabber.Loaded.connect(self.dataReady, Qt.QueuedConnection)
        if self.dataError is not None:
            grabber.Error.connect(self.dataError, Qt.QueuedConnection)

        try:
            result = self.method(*self.args)
            if self.cancelled:
                # cleanup happens in 'finally' statement
                return
            grabber.Loaded.emit(self.uid, result)
        except Exception as error:
            if self.cancelled:
                # cleanup happens in 'finally' statement
                return
            grabber.Error.emit(self.uid, unicode(error))
        finally:
            # this will run even if one of the above return statements
            # is executed inside of the try/except statement see:
            # https://docs.python.org/2.7/tutorial/errors.html#defining-clean-up-actions
            self.cleanup(grabber)

    def cleanup(self, grabber=None):
        # remove references to any object or method for proper ref counting
        self.method = None
        self.args = None
        self.uid = None
        self.dataReady = None
        self.dataError = None

        if grabber is not None:
            grabber.deleteLater()

        # make sure this python obj gets cleaned up
        self.remove()

    def remove(self):
        try:
            Request.INSTANCES.remove(self)

            # when the next request is created, it will clean this one up
            # this will help us avoid this object being cleaned up
            # when it's still being used
            Request.FINISHED.append(self)
        except ValueError:
            # there might be a race condition on shutdown, when shutdown()
            # is called while the thread is still running and the instance
            # has already been removed from the list
            return

    @staticmethod
    def shutdown():
        for inst in Request.INSTANCES:
            inst.cancelled = True
        Request.INSTANCES = []
        Request.FINISHED = []


class Requester(QtCore.QObject):
    """
    A simple object designed to be used in a separate thread to allow
    for asynchronous data fetching
    """

    #
    # Signals
    #

    Error = QtCore.pyqtSignal(object, unicode)
    """
    Emitted if the fetch fails for any reason

    :param unicode uid: an id to identify this request
    :param unicode error: the error message
    """

    Loaded = QtCore.pyqtSignal(object, object)
    """
    Emitted whenever data comes back successfully

    :param unicode uid: an id to identify this request
    :param list data: the json list returned from the GET
    """

    NetworkConnectionError = QtCore.pyqtSignal(unicode)
    """
    Emitted when the task fails due to a network connection error

    :param unicode message: network connection error message
    """

    def __init__(self, parent=None):
        super(Requester, self).__init__(parent)


class ExampleObject(QtCore.QObject):
    def __init__(self, parent=None):
        super(ExampleObject, self).__init__(parent)
        self.uid = 0
        self.request = None

    def ready_callback(self, uid, result):
        if uid != self.uid:
            return
        print "Data ready from %s: %s" % (uid, result)

    def error_callback(self, uid, error):
        if uid != self.uid:
            return
        print "Data error from %s: %s" % (uid, error)

    def fetch(self):
        if self.request is not None:
            # cancel any pending requests
            self.request.cancelled = True
            self.request = None

        self.uid += 1
        self.request = async(slow_method, ["arg1", "arg2"], self.uid,
                             self.ready_callback,
                             self.error_callback)


def slow_method(arg1, arg2):
    print "Starting slow method"
    time.sleep(1)
    return arg1 + arg2


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)

    obj = ExampleObject()

    dialog = QtGui.QDialog()
    layout = QtGui.QVBoxLayout(dialog)
    button = QtGui.QPushButton("Generate", dialog)
    progress = QtGui.QProgressBar(dialog)
    progress.setRange(0, 0)
    layout.addWidget(button)
    layout.addWidget(progress)
    button.clicked.connect(obj.fetch)
    dialog.show()

    app.exec_()
    app.deleteLater() # avoids some QThread messages in the shell on exit
    # cancel all running tasks avoid QThread/QTimer error messages
    # on exit
    Request.shutdown()

退出应用程序时,您需要确保取消所有任务,否则应用程序将挂起,直到每个计划任务都完成

【讨论】:

这是一个很好的答案——谢谢!有一篇博文Multithreading PyQt applications with QThreadPool 有类似的方法。【参考方案6】:

基于其他答案中提到的 Worker 对象方法,我决定看看我是否可以扩展解决方案以调用更多线程 - 在这种情况下,机器可以运行并启动多个具有不确定完成时间的工作人员的最佳数量。 为此,我仍然需要对 QThread 进行子类化——但只需分配一个线程号并“重新实现”信号“完成”和“开始”以包含它们的线程号。

我非常关注主 gui、线程和工作程序之间的信号。

类似地,其他人的回答一直很难指出不养育 QThread,但我认为这不是一个真正的问题。但是,我的代码也很小心地销毁了 QThread 对象。

但是,我无法将工作对象作为父对象,因此似乎需要在线程函数完成或 GUI 被破坏时向它们发送 deleteLater() 信号。我自己的代码因为不这样做而被挂起。

我认为有必要的另一项改进是重新实现 GUI (QWidget) 的 closeEvent,以便指示线程退出,然后 GUI 将等待所有线程完成。当我使用这个问题的其他一些答案时,我得到了 QThread 被破坏的错误。

也许它对其他人有用。我当然发现这是一个有用的练习。也许其他人会知道一个线程宣布其身份的更好方法。

#!/usr/bin/env python3
#coding:utf-8
# Author:   --<>
# Purpose:  To demonstrate creation of multiple threads and identify the receipt of thread results
# Created: 19/12/15

import sys


from PyQt4.QtCore import QThread, pyqtSlot, pyqtSignal
from PyQt4.QtGui import QApplication, QLabel, QWidget, QGridLayout

import sys
import worker

class Thread(QThread):
    #make new signals to be able to return an id for the thread
    startedx = pyqtSignal(int)
    finishedx = pyqtSignal(int)

    def __init__(self,i,parent=None):
        super().__init__(parent)
        self.idd = i

        self.started.connect(self.starttt)
        self.finished.connect(self.finisheddd)

    @pyqtSlot()
    def starttt(self):
        print('started signal from thread emitted')
        self.startedx.emit(self.idd) 

    @pyqtSlot()
    def finisheddd(self):
        print('finished signal from thread emitted')
        self.finishedx.emit(self.idd)

class Form(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()

        self.worker=
        self.threadx=
        self.i=0
        i=0

        #Establish the maximum number of threads the machine can optimally handle
        #Generally relates to the number of processors

        self.threadtest = QThread(self)
        self.idealthreadcount = self.threadtest.idealThreadCount()

        print("This machine can handle  threads optimally".format(self.idealthreadcount))

        while i <self.idealthreadcount:
            self.setupThread(i)
            i+=1

        i=0
        while i<self.idealthreadcount:
            self.startThread(i)
            i+=1

        print("Main Gui running in thread .".format(self.thread()))


    def setupThread(self,i):

        self.worker[i]= worker.Worker(i)  # no parent!
        #print("Worker object runningt in thread  prior to movetothread".format(self.worker[i].thread()) )
        self.threadx[i] = Thread(i,parent=self)  #  if parent isn't specified then need to be careful to destroy thread 
        self.threadx[i].setObjectName("python thread"+str(i))
        #print("Thread object runningt in thread  prior to movetothread".format(self.threadx[i].thread()) )
        self.threadx[i].startedx.connect(self.threadStarted)
        self.threadx[i].finishedx.connect(self.threadFinished)

        self.worker[i].finished.connect(self.workerFinished)
        self.worker[i].intReady.connect(self.workerResultReady)

        #The next line is optional, you may want to start the threads again without having to create all the code again.
        self.worker[i].finished.connect(self.threadx[i].quit)

        self.threadx[i].started.connect(self.worker[i].procCounter)

        self.destroyed.connect(self.threadx[i].deleteLater)
        self.destroyed.connect(self.worker[i].deleteLater)

        #This is the key code that actually get the worker code onto another processor or thread.
        self.worker[i].moveToThread(self.threadx[i])

    def startThread(self,i):
        self.threadx[i].start()

    @pyqtSlot(int)
    def threadStarted(self,i):
        print('Thread   started'.format(i))
        print("Thread priority is ".format(self.threadx[i].priority()))        


    @pyqtSlot(int)
    def threadFinished(self,i):
        print('Thread  finished'.format(i))




    @pyqtSlot(int)
    def threadTerminated(self,i):
        print("Thread  terminated".format(i))

    @pyqtSlot(int,int)
    def workerResultReady(self,j,i):
        print('Worker  result returned'.format(i))
        if i ==0:
            self.label1.setText("".format(j))
        if i ==1:
            self.label2.setText("".format(j))
        if i ==2:
            self.label3.setText("".format(j))
        if i ==3:
            self.label4.setText("".format(j)) 

        #print('Thread  has started'.format(self.threadx[i].currentThreadId()))    

    @pyqtSlot(int)
    def workerFinished(self,i):
        print('Worker  finished'.format(i))

    def initUI(self):
        self.label1 = QLabel("0")
        self.label2= QLabel("0")
        self.label3= QLabel("0")
        self.label4 = QLabel("0")
        grid = QGridLayout(self)
        self.setLayout(grid)
        grid.addWidget(self.label1,0,0)
        grid.addWidget(self.label2,0,1) 
        grid.addWidget(self.label3,0,2) 
        grid.addWidget(self.label4,0,3) #Layout parents the self.labels

        self.move(300, 150)
        self.setGeometry(0,0,300,300)
        #self.size(300,300)
        self.setWindowTitle('thread test')
        self.show()

    def closeEvent(self, event):
        print('Closing')

        #this tells the threads to stop running
        i=0
        while i <self.idealthreadcount:
            self.threadx[i].quit()
            i+=1

         #this ensures window cannot be closed until the threads have finished.
        i=0
        while i <self.idealthreadcount:
            self.threadx[i].wait() 
            i+=1        


        event.accept()


if __name__=='__main__':
    app = QApplication(sys.argv)
    form = Form()
    sys.exit(app.exec_())

以及下面的工人代码

#!/usr/bin/env python3
#coding:utf-8
# Author:   --<>
# Purpose:  Stack Overflow
# Created: 19/12/15

import sys
import unittest


from PyQt4.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
import random


class Worker(QObject):
    finished = pyqtSignal(int)
    intReady = pyqtSignal(int,int)

    def __init__(self, i=0):
        '''__init__ is called while the worker is still in the Gui thread. Do not put slow or CPU intensive code in the __init__ method'''

        super().__init__()
        self.idd = i



    @pyqtSlot()
    def procCounter(self): # This slot takes no params
        for j in range(1, 10):
            random_time = random.weibullvariate(1,2)
            time.sleep(random_time)
            self.intReady.emit(j,self.idd)
            print('Worker 0 in thread 1'.format(self.idd, self.thread().idd))

        self.finished.emit(self.idd)


if __name__=='__main__':
    unittest.main()

【讨论】:

【参考方案7】:

PySide2 解决方案:

与 PyQt5 不同,在 PySide2 中,QThread.started 信号是在原始线程上接收/处理的,而不是在工作线程上!幸运的是,它仍然在工作线程上接收所有其他信号。

为了匹配 PyQt5 的行为,你必须自己创建启动信号。

这是一个简单的解决方案:

# Use this class instead of QThread
class QThread2(QThread):
    # Use this signal instead of "started"
    started2 = Signal()

    def __init__(self):
        QThread.__init__(self)
        self.started.connect(self.onStarted)

    def onStarted(self):
        self.started2.emit()

【讨论】:

以上是关于PyQt中带有QThread的后台线程的主要内容,如果未能解决你的问题,请参考以下文章

Qthread 不工作,GUI 仍然挂起

PyQt5 UI 制作一个豆瓣电影信息查看器,初识QThread多线程...

PyQT QGraphicScene 在后台线程中移动项目

[ PyQt入门教程 ] PyQt5中多线程模块QThread使用方法

PyQt:如何从 QThread 获取 UI 数据

PyQt中QThread多线程的正确用法