繁体   English   中英

如何通过 moveToThread() 在 pyqt 中正确使用 QThread?

[英]How to use QThread correctly in pyqt with moveToThread()?

我阅读了这篇文章如何真正、真正地使用 QThreads; 完整的解释,它说而不是子类 qthread,并重新实现 run(),应该使用 moveToThread 将 QObject 推送到使用 moveToThread(QThread*) 的 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,

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

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

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

重要提示: QThread 对象不属于它自己的线程[ docs ]:

重要的是要记住 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 的示例,但我的代码一直在我的主线程中运行! 这就是他宣布调用run的信号的方式!

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

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

我已经尝试过@qris 和@MatthewRunchey 方法。

使用@pyqtSlot装饰器,Qt 在发出信号时检查工作实例的“位置”:即使在moveToThread在工作线程中执行槽之后moveToThread信号之前建立了连接。

如果没有@pyqtSlot装饰器,Qt 会在建立连接的那一刻冻结工作实例的“位置”:如果它在moveToThread之前,它会绑定到主线程,并且插槽代码会继续在主线程中执行,即使在moveToThread调用后发出信号。

moveToThread之后建立的连接在这两种情况下都会绑定要执行的工作线程的插槽。

代码:

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

class Worker(QObject):
    def __init__(self):
        super(Worker, self).__init__()
#        self.call_f1.connect(self.f1)
#        self.call_f2.connect(self.f2)

    call_f1 = pyqtSignal()
    call_f2 = pyqtSignal()

    @pyqtSlot()
    def f1(self):
        print('f1', threading.get_ident())
    
    @pyqtSlot()
    def f2(self):
        print('f2', threading.get_ident())

app = QCoreApplication([])
print('main', threading.get_ident())
my_thread = QThread()
my_thread.start()

my_worker = Worker()
my_worker.call_f1.connect(my_worker.f1)
my_worker.call_f1.emit()
my_worker.moveToThread(my_thread)
my_worker.call_f2.connect(my_worker.f2)
my_worker.call_f1.emit()
my_worker.call_f2.emit()
sys.exit(app.exec_())

带装饰器:

main 18708
f1 18708
f1 20156
f2 20156

没有装饰器:

main 5520
f1 5520
f1 5520
f2 11472

PS 在worker __init__方法中连接显然相当于在主线程中moveToThread之前连接。

(在 PyQt5、win64 下测试)。

我知道这是一个老问题,但我发现这篇相关且相当新的文章: 使用 PyQt 的 QThread 防止冻结 GUI

它几乎是如何真正、真正使用 QThreads中给出的方法的最小 Python 实现,它提供了一个非常简洁的示例来说明如何使用QObject.moveToThread()

我将代码复制并粘贴在这里以供参考和讨论(我不是作者):

from PyQt5.QtCore import QObject, QThread, pyqtSignal
# Snip...

# Step 1: Create a worker class class Worker(QObject):
class Worker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)

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

class Window(QMainWindow):
    # Snip...
    def runLongTask(self):
        # Step 2: Create a QThread object
        self.thread = 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()

虽然接受的答案确实有效,但我认为上述方法非常可取,因为

  • 工作人员完成后,它会正确quit线程,
  • 它使用.deleteLater对线程和工作线程执行清理,并且
  • 它不依赖pyqtSlot()装饰器在另一个线程中实际运行(我对此进行了测试)。

注意:在上述方法中,线程——以及连接到thread.started信号的工作程序——是“手动”启动的,工作人员在其工作完成时发出finished信号。 然而,在接受的答案中,工人是通过start信号启动的,但没有机制表明其工作是否完成。

我正在测试Anthony 的答案,并注意到即使在 f1 或 f2 方法中定义变量名也无法获得发射值。 它给出了“ TypeError: Worker.f1() missing 1 required positional argument: 'variable' ” 错误。花了 1 个小时才注意到您需要确定发送到插槽的数据类型。 我只是在这里提醒谁在复制粘贴代码并为此苦苦挣扎..

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

class Worker(QObject):
    def __init__(self):
        super(Worker, self).__init__()
#        self.call_f1.connect(self.f1)
#        self.call_f2.connect(self.f2)

    call_f1 = pyqtSignal(object)<--- add type of data you want to get
    call_f2 = pyqtSignal()

    @pyqtSlot(object)  <--- add type of data you want to get
    def f1(self,variable):
        print('f1',variable, threading.get_ident()) <-- now you can get it
    
    @pyqtSlot()
    def f2(self):
        print('f2', threading.get_ident())

app = QCoreApplication([])
print('main', threading.get_ident())
my_thread = QThread()
my_thread.start()

my_worker = Worker()
my_worker.call_f1.connect(my_worker.f1)
my_worker.moveToThread(my_thread)
my_worker.call_f1.emit("test data") <--- Now you can add your data to emit 
my_worker.call_f2.connect(my_worker.f2)
my_worker.call_f1.emit()
my_worker.call_f2.emit()
sys.exit(app.exec_())

暂无
暂无

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

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