如何在带有 moveToThread() 的 pyqt 中正确使用 QThread?

新手上路,请多包涵

我阅读了这篇文章 How To Really, Truly Use QThreads;完整的解释,它说不是子类 qthread,而是重新实现 run(),应该使用 moveToThread 将 QObject 推到 QThread 实例上,使用 moveToThread(QThread*)

这是 c++ 示例,但我不知道如何将它转换为 python 代码。

 class Worker : public QObject
 {
     Q_OBJECT
     QThread workerThread;

 public slots:
     void doWork(const QString &parameter) {
         // ...
         emit resultReady(result);
     }

 signals:
     void resultReady(const QString &result);
 };

 class Controller : public QObject
 {
     Q_OBJECT
     QThread workerThread;
 public:
     Controller() {
         Worker *worker = new Worker;
         worker->moveToThread(&workerThread);
         connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
         connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString)));
         connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
         workerThread.start();
     }
     ~Controller() {
         workerThread.quit();
         workerThread.wait();
     }
 public slots:
     void handleResults(const QString &);
 signals:
     void operate(const QString &);
 };

QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(thread, SIGNAL(started()), worker, SLOT(process()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();

我一直在使用这种方法生成一个 qthread ,但正如您所看到的,它使用的是不推荐的方式。我如何重写它以使用首选方法?

 class GenericThread(QThread):
    def __init__(self, function, *args, **kwargs):
        QThread.__init__(self)
        # super(GenericThread, self).__init__()

        self.function = function
        self.args = args
        self.kwargs = kwargs

    def __del__(self):
        self.wait()

    def run(self, *args):
        self.function(*self.args, **self.kwargs)

编辑:两年后……我尝试了 qris 的代码,它在不同的线程中工作

import sys
import time
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSignal, pyqtSlot
import threading

def logthread(caller):
    print('%-25s: %s, %s,' % (caller, threading.current_thread().name,
                              threading.current_thread().ident))

class MyApp(QtGui.QWidget):

    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        self.setGeometry(300, 300, 280, 600)
        self.setWindowTitle('using threads')

        self.layout = QtGui.QVBoxLayout(self)

        self.testButton = QtGui.QPushButton("QThread")
        self.testButton.released.connect(self.test)
        self.listwidget = QtGui.QListWidget(self)

        self.layout.addWidget(self.testButton)
        self.layout.addWidget(self.listwidget)

        self.threadPool = []
        logthread('mainwin.__init__')

    def add(self, text):
        """ Add item to list widget """
        logthread('mainwin.add')
        self.listwidget.addItem(text)
        self.listwidget.sortItems()

    def addBatch(self, text="test", iters=6, delay=0.3):
        """ Add several items to list widget """
        logthread('mainwin.addBatch')
        for i in range(iters):
            time.sleep(delay)  # artificial time delay
            self.add(text+" "+str(i))

    def test(self):
        my_thread = QtCore.QThread()
        my_thread.start()

        # This causes my_worker.run() to eventually execute in my_thread:
        my_worker = GenericWorker(self.addBatch)
        my_worker.moveToThread(my_thread)
        my_worker.start.emit("hello")
        # my_worker.finished.connect(self.xxx)

        self.threadPool.append(my_thread)
        self.my_worker = my_worker

class GenericWorker(QtCore.QObject):

    start = pyqtSignal(str)
    finished = pyqtSignal()

    def __init__(self, function, *args, **kwargs):
        super(GenericWorker, self).__init__()
        logthread('GenericWorker.__init__')
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.start.connect(self.run)

    @pyqtSlot()
    def run(self, *args, **kwargs):
        logthread('GenericWorker.run')
        self.function(*self.args, **self.kwargs)
        self.finished.emit()

# run
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
app.exec_()

输出是:

 mainwin.__init__         : MainThread, 140221684574016,
GenericWorker.__init__   : MainThread, 140221684574016,
GenericWorker.run        : Dummy-1, 140221265458944,
mainwin.addBatch         : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,

原文由 Shuman 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.4k
2 个回答

QThread 中默认的 run() 实现为你运行一个事件循环,相当于:

 class GenericThread(QThread):
    def run(self, *args):
        self.exec_()

事件循环的重要之处在于它允许线程 拥有的 对象在它们的槽中接收事件,这些事件将 在该线程 中执行。这些对象只是 QObjects,而不是 QThreads。

重要说明:QThread 对象 不属于它自己的线程[ 文档]:

重要的是要记住 QThread 实例存在于实例化它的旧线程中,而不是存在于调用 run() 的新线程中。这意味着所有 QThread 的排队槽和调用的方法都将在旧线程中执行 [例如主线程]。

所以你应该能够做到这一点:

 class GenericWorker(QObject):
    def __init__(self, function, *args, **kwargs):
        super(GenericWorker, self).__init__()

        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.start.connect(self.run)

    start = pyqtSignal(str)

    @pyqtSlot()
    def run(self, some_string_arg):
        self.function(*self.args, **self.kwargs)

my_thread = QThread()
my_thread.start()

# This causes my_worker.run() to eventually execute in my_thread:
my_worker = GenericWorker(...)
my_worker.moveToThread(my_thread)
my_worker.start.emit("hello")

另外,请仔细考虑 self.function 的结果会发生什么,目前已丢弃。您可以在 GenericWorker 上声明另一个信号,它接收结果,并让 run() 方法在完成时发出该信号,将结果传递给它。

一旦掌握了它并意识到您不需要也不应该子类化 QThread,生活就会变得更加直接和轻松。简单地说,永远不要在 QThread 中工作。你应该几乎永远不需要覆盖运行。对于大多数用例,将 QObject 与 QThread 建立适当的关联并使用 QT 的信号/槽创建了一种非常强大的多线程编程方式。请注意不要让您推送到工作线程的 QObjects 闲置……

http://ilearnstuff.blogspot.co.uk/2012/09/qthread-best-practices-when-qthread.html

原文由 qris 发布,翻译遵循 CC BY-SA 4.0 许可协议

我试图在我的应用程序中使用 qris 的示例,但我的代码一直在我的主线程中运行!这是他宣布调用运行的 信号 的方式!

基本上,当您在对象的构造函数中连接它时,连接将存在 于主线程中的两个对象之间——因为 QObject 的属性属于创建它们的线程。当您将 QObject 移动到新线程时, 连接不会随您移动。拿走将信号连接到运行函数的线,并 在将工作人员移动到它的新线程后连接它!

qris 回答的相关变化:

 class GenericWorker(QObject):
    def __init__(self, function, *args, **kwargs):
        super(GenericWorker, self).__init__()

        self.function = function
        self.args = args
        self.kwargs = kwargs

    start = pyqtSignal(str)

    @pyqtSlot
    def run(self, some_string_arg):
        self.function(*self.args, **self.kwargs)

my_thread = QThread()
my_thread.start()

# This causes my_worker.run() to eventually execute in my_thread:
my_worker = GenericWorker(...)
my_worker.moveToThread(my_thread)
my_worker.start.connect(my_worker.run) #  <---- Like this instead
my_worker.start.emit("hello")

原文由 Matthew Runchey 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏