简体   繁体   English

PyQt 中带有 QThread 的后台线程

[英]Background thread with QThread in PyQt

I have a program which interfaces with a radio I am using via a gui I wrote in PyQt.我有一个程序,它通过我在 PyQt 中编写的 gui 与我正在使用的收音机连接。 Obviously one of the main functions of the radio is to transmit data, but to do this continuously, I have to loop the writes, which causes the gui to hang.显然无线电的主要功能之一是传输数据,但要连续执行此操作,我必须循环写入,这会导致 gui 挂起。 Since I have never dealt with threading, I tried to get rid of these hangs using QCoreApplication.processEvents().由于我从未处理过线程,因此我尝试使用QCoreApplication.processEvents(). The radio needs to sleep between transmissions, though, so the gui still hangs based on how long these sleeps last.但是,无线电需要在传输之间休眠,因此 gui 仍会根据这些休眠持续的时间而挂起。

Is there a simple way to fix this using QThread?有没有使用 QThread 解决此问题的简单方法? I have looked for tutorials on how to implement multithreading with PyQt, but most of them deal with setting up servers and are much more advanced than I need them to be.我一直在寻找有关如何使用 PyQt 实现多线程的教程,但其中大多数都涉及设置服务器并且比我需要的要先进得多。 I honestly don't even really need my thread to update anything while it is running, I just need to start it, have it transmit in the background, and stop it.老实说,我什至不需要我的线程在运行时更新任何东西,我只需要启动它,让它在后台传输,然后停止它。

I created a little example that shows 3 different and simple ways of dealing with threads.我创建了一个小示例,展示了处理线程的 3 种不同且简单的方法。 I hope it will help you find the right approach to your problem.我希望它能帮助您找到解决问题的正确方法。

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

Take this answer updated for PyQt5, python 3.4将此答案更新为 PyQt5、python 3.4

Use this as a pattern to start a worker that does not take data and return data as they are available to the form.使用它作为一种模式来启动一个不获取数据并在表单可用时返回数据的工作程序。

1 - Worker class is made smaller and put in its own file worker.py for easy memorization and independent software reuse. 1 - Worker class 变小并放入自己的文件worker.py 以便于记忆和独立软件重用。

2 - The main.py file is the file that defines the GUI Form class 2 - main.py 文件是定义 GUI 表单 class 的文件

3 - The thread object is not subclassed. 3 - 线程 object 没有子类化。

4 - Both thread object and the worker object belong to the Form object 4 - 线程 object 和工作线程 object 都属于表格 object

5 - Steps of the procedure are within the comments. 5 - 程序的步骤在评论中。

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

And the main file is:主要文件是:

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

Very nice example from Matt, I fixed the typo and also pyqt4.8 is common now so I removed the dummy class as well and added an example for the dataReady signal来自 Matt 的非常好的示例,我修复了错字,而且 pyqt4.8 现在也很常见,所以我也删除了虚拟 class 并添加了一个 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_()

According to the Qt developers, subclassing QThread is incorrect (see http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/ ).根据 Qt 开发人员的说法,子类化 QThread 是不正确的(请参阅http://blog.qt.io/blog/2010/06/17/youre-doing-doing )。 But that article is really hard to understand (plus the title is a bit condescending).但是那篇文章真的很难理解(加上标题有点居高临下)。 I found a better blog post that gives a more detailed explanation about why you should use one style of threading over another: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/我发现了一篇更好的博客文章,它更详细地解释了为什么应该使用一种线程样式而不是另一种线程: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use- qthreads-the-full-explanation/

In my opinion, you should probably never subclass thread with the intent to overload the run method.在我看来,您可能永远不应该为了重载 run 方法而对线程进行子类化。 While that does work, you're basically circumventing how Qt wants you to work.虽然这确实有效,但您基本上是在规避 Qt 希望您如何工作。 Plus you'll miss out on things like events and proper thread safe signals and slots.另外,您会错过诸如事件和适当的线程安全信号和插槽之类的事情。 Plus as you'll likely see in the above blog post, the "correct" way of threading forces you to write more testable code.另外,正如您可能会在上面的博客文章中看到的那样,“正确”的线程方式会迫使您编写更多可测试的代码。

Here's a couple of examples of how to take advantage of QThreads in PyQt (I posted a separate answer below that properly uses QRunnable and incorporates signals/slots, that answer is better if you have a lot of async tasks that you need to load balance).下面是几个如何利用 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()        

In PyQt there are a lot of options for getting asynchronous behavior.在 PyQt 中有很多用于获取异步行为的选项。 For things that need event processing (ie. QtNetwork, etc) you should use the QThread example I provided in my other answer on this thread.对于需要事件处理的事情(即 QtNetwork 等),您应该使用我在此线程的其他答案中提供的 QThread 示例。 But for the vast majority of your threading needs, I think this solution is far superior than the other methods.但是对于绝大多数线程需求,我认为这个解决方案比其他方法要优越得多。

The advantage of this is that the QThreadPool schedules your QRunnable instances as tasks.这样做的好处是 QThreadPool 将您的 QRunnable 实例安排为任务。 This is similar to the task pattern used in Intel's TBB.这类似于英特尔 TBB 中使用的任务模式。 It's not quite as elegant as I like but it does pull off excellent asynchronous behavior.它不像我喜欢的那样优雅,但它确实实现了出色的异步行为。

This allows you to utilize most of the threading power of Qt in Python via QRunnable and still take advantage of signals and slots.这使您可以通过 QRunnable 在 Python 中利用 Qt 的大部分线程功能,并且仍然可以利用信号和插槽。 I use this same code in several applications, some that make hundreds of asynchronous REST calls, some that open files or list directories, and the best part is using this method, Qt task balances the system resources for me.我在几个应用程序中使用相同的代码,其中一些进行数百次异步 REST 调用,一些打开文件或列出目录,最好的部分是使用这种方法,ZE8801102A40AD89DDCFDCAEBF008D25Z 任务为我平衡了系统资源。

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

When exiting the application you'll want to make sure you cancel all of the tasks or the application will hang until every scheduled task has completed退出应用程序时,您需要确保取消所有任务,否则应用程序将挂起,直到每个计划任务完成

Based on the Worker objects methods mentioned in other answers, I decided to see if I could expand on the solution to invoke more threads - in this case the optimal number the machine can run and spin up multiple workers with indeterminate completion times.基于其他答案中提到的 Worker 对象方法,我决定看看我是否可以扩展解决方案以调用更多线程 - 在这种情况下,机器可以运行并启动多个具有不确定完成时间的工作人员的最佳数量。 To do this I still need to subclass QThread - but only to assign a thread number and to 'reimplement' the signals 'finished' and 'started' to include their thread number.为此,我仍然需要对 QThread 进行子类化——但只需分配一个线程号并“重新实现”信号“完成”和“开始”以包含它们的线程号。

I've focused quite a bit on the signals between the main gui, the threads, and the workers.我非常关注主 gui、线程和工作人员之间的信号。

Similarly, others answers have been a pains to point out not parenting the QThread but I don't think this is a real concern.同样,其他人的答案一直很难指出没有养育 QThread,但我认为这不是一个真正的问题。 However, my code also is careful to destroy the QThread objects.但是,我的代码也很小心地破坏了 QThread 对象。

However, I wasn't able to parent the worker objects so it seems desirable to send them the deleteLater() signal, either when the thread function is finished or the GUI is destroyed.但是,我无法将工作对象作为父对象,因此在线程 function 完成或 GUI 被破坏时,向它们发送 deleteLater() 信号似乎是可取的。 I've had my own code hang for not doing this.我有自己的代码因为不这样做而挂起。

Another enhancement I felt was necessary was was reimplement the closeEvent of the GUI (QWidget) such that the threads would be instructed to quit and then the GUI would wait until all the threads were finished.我认为有必要的另一个改进是重新实现 GUI (QWidget) 的 closeEvent,以便指示线程退出,然后 GUI 将等待所有线程完成。 When I played with some of the other answers to this question, I got QThread destroyed errors.当我玩这个问题的其他一些答案时,我得到了 QThread 被破坏的错误。

Perhaps it will be useful to others.也许它对其他人有用。 I certainly found it a useful exercise.我当然发现这是一个有用的练习。 Perhaps others will know a better way for a thread to announce it identity.也许其他人会知道线程宣布其身份的更好方法。

#!/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_())

And the worker code below以及下面的工人代码

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

PySide2 Solution: PySide2 解决方案:

Unlike in PyQt5, in PySide2 the QThread.started signal is received/handled on the original thread, not the worker thread.与 PyQt5 不同,在 PySide2 中,QThread.started 信号在原始线程而不是工作线程上接收/处理。 Luckily it still receives all other signals on the worker thread.幸运的是,它仍然接收工作线程上的所有其他信号。

In order to match PyQt5's behavior, you have to create the started signal yourself.为了匹配 PyQt5 的行为,您必须自己创建启动信号。

Here is an easy solution:这是一个简单的解决方案:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM