简体   繁体   English

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

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

i read this article How To Really, Truly Use QThreads;我阅读了这篇文章如何真正、真正地使用 QThreads; The Full Explanation , it says instead of subclass qthread, and reimplement run(), one should use moveToThread to push a QObject onto QThread instance using moveToThread(QThread*) 完整的解释,它说而不是子类 qthread,并重新实现 run(),应该使用 moveToThread 将 QObject 推送到使用 moveToThread(QThread*) 的 QThread 实例

here is the c++ example, but i don't know how to convert it to python code.这是 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();

i've been using this method to generate a qthread , but as you can see, it's using the not recommended way.我一直在使用这种方法来生成一个 qthread ,但是正如你所看到的,它使用的是不推荐的方式。 how can i re-write it to use the preferred method ?我如何重写它以使用首选方法?

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)

edit: two years later ... I tried qris' code, it works and in different thread编辑:两年后...我尝试了 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_()

the ouput is:输出是:

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,

The default run() implementation in QThread runs an event loop for you, the equivalent of: QThread 中默认的 run() 实现会为您运行一个事件循环,相当于:

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

The important thing about an event loop is that it allows objects owned by the thread to receive events on their slots, which will be executed in that thread .事件循环的重要之处在于它允许线程拥有的对象在其插槽上接收事件,这些事件将在该线程中执行。 Those objects are just QObjects, not QThreads.这些对象只是 QObjects,而不是 QThreads。

Important note: the QThread object is not owned by its own thread [ docs ]:重要提示: QThread 对象不属于它自己的线程[ docs ]:

It is important to remember that a QThread instance lives in the old thread that instantiated it, not in the new thread that calls run().重要的是要记住 QThread 实例存在于实例化它的旧线程中,而不是调用 run() 的新线程中。 This means that all of QThread's queued slots and invoked methods will execute in the old thread [eg the main thread].这意味着QThread 的所有排队槽和调用的方法都将在旧线程[例如主线程] 中执行。

So you should be able to do this:所以你应该能够做到这一点:

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

Also, think carefully about what happens with the result of self.function , which is currently discarded.另外,请仔细考虑self.function的结果会发生什么,该结果目前已被丢弃。 You could declare another signal on GenericWorker , which receives the result, and have the run() method emit that signal when it's done, passing the result to it.您可以在GenericWorker上声明另一个信号,该信号接收结果,并让run()方法在完成时发出该信号,并将结果传递给它。

Once you get the hang of it and realize you don't and shouldn't subclass QThread, life becomes a lot more straightforward and easier.一旦你掌握了它并意识到你没有也不应该继承 QThread,生活就会变得更加简单和轻松。 Simply put, never do work in QThread.简单地说,永远不要在 QThread 中工作。 You should almost never need to override run.您几乎不需要覆盖运行。 For most use cases, setting up proper associations with a QObject to a QThread and using QT's signals/slots creates an extremely powerful way to do multithreaded programming.对于大多数用例,将 QObject 与 QThread 建立适当的关联并使用 QT 的信号/插槽创建了一种非常强大的多线程编程方式。 Just be careful not to let the QObjects you've pushed to your worker threads hang around...请注意不要让您推送到工作线程的 QObjects 徘徊......

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

I was attempting to use qris's example in my application, but kept having my code run in the my main thread!我试图在我的应用程序中使用 qris 的示例,但我的代码一直在我的主线程中运行! It is the way the signal that he declared to call run!这就是他宣布调用run的信号的方式!

Basically, when you connect it in the constructor of the object, the connection will exist between two objects in the main thread - because the QObject's properties belong to the thread that created them .基本上,当您在对象的构造函数中连接它时,主线程中的两个对象之间将存在连接——因为 QObject 的属性属于创建它们的线程 When you move the QObject to your new thread, the connection doesn't move with you .当您将 QObject 移动到新线程时,连接不会随您移动 Take away the line that connects your signal to the run function, and connect it after you move the worker to its new thread!拿走将信号连接到 run 函数的线,并在将 worker 移动到其新线程后将其连接!

The relevant change from qris's answer: 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")

I've tried both @qris and @MatthewRunchey approaches.我已经尝试过@qris 和@MatthewRunchey 方法。

With the @pyqtSlot decorator Qt checks the "location" of the worker instance when the signal is emitted: even if the connection was made before moveToThread emitting the signal after moveToThread executes the slot in the worker thread.使用@pyqtSlot装饰器,Qt 在发出信号时检查工作实例的“位置”:即使在moveToThread在工作线程中执行槽之后moveToThread信号之前建立了连接。

Without the @pyqtSlot decorator Qt freezes the "location" of the worker instance the moment when the connection was made: if it was before moveToThread , it is bound to the main thread, and the slot code keeps being executed in the main thread even if the signal is emitted after moveToThread call.如果没有@pyqtSlot装饰器,Qt 会在建立连接的那一刻冻结工作实例的“位置”:如果它在moveToThread之前,它会绑定到主线程,并且插槽代码会继续在主线程中执行,即使在moveToThread调用后发出信号。

Connections made after moveToThread bind the slot to be executed the worker thread in both cases.moveToThread之后建立的连接在这两种情况下都会绑定要执行的工作线程的插槽。

Code:代码:

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

With decorator:带装饰器:

main 18708
f1 18708
f1 20156
f2 20156

Without decorator:没有装饰器:

main 5520
f1 5520
f1 5520
f2 11472

PS Connecting in the worker __init__ method is obviously equivalent to connecting before moveToThread in the main thread. PS 在worker __init__方法中连接显然相当于在主线程中moveToThread之前连接。

(tested under PyQt5, win64). (在 PyQt5、win64 下测试)。

I know this is an old question, but I found this relevant and fairly recent article: Use PyQt's QThread to Prevent Freezing GUIs .我知道这是一个老问题,但我发现这篇相关且相当新的文章: 使用 PyQt 的 QThread 防止冻结 GUI

It is pretty much the minimal Python implementation of the approach given in How To Really, Truly Use QThreads and it provides a very clean example of how to use QObject.moveToThread() .它几乎是如何真正、真正使用 QThreads中给出的方法的最小 Python 实现,它提供了一个非常简洁的示例来说明如何使用QObject.moveToThread()

I copied the code and pasted it here for reference and discussion (I'm not the author):我将代码复制并粘贴在这里以供参考和讨论(我不是作者):

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

While the accepted answer does work, I think the above approach is highly preferable, because虽然接受的答案确实有效,但我认为上述方法非常可取,因为

  • it properly quit s the thread when the worker has finished,工作人员完成后,它会正确quit线程,
  • it performs clean-up for both the thread and the worker using .deleteLater , and它使用.deleteLater对线程和工作线程执行清理,并且
  • it does not rely on the pyqtSlot() decorator to actually run in another thread (I tested this).它不依赖pyqtSlot()装饰器在另一个线程中实际运行(我对此进行了测试)。

Note: In the above approach, the thread – and thus the worker, being connected to the thread.started signal – is started "manually", and the worker emits a finished signal when its work is done.注意:在上述方法中,线程——以及连接到thread.started信号的工作程序——是“手动”启动的,工作人员在其工作完成时发出finished信号。 However, in the accepted answer, the worker is started through a start signal, but there is no mechanism indicating whether its work is done.然而,在接受的答案中,工人是通过start信号启动的,但没有机制表明其工作是否完成。

i was testing Anthony's answer and noticed that cant get emitted value even with defining variable name in f1 or f2 methods.我正在测试Anthony 的答案,并注意到即使在 f1 或 f2 方法中定义变量名也无法获得发射值。 it was giving " TypeError: Worker.f1() missing 1 required positional argument: 'variable' " error.it took 1 hour to notice that you need to determine type of emitted data to the slot.它给出了“ TypeError: Worker.f1() missing 1 required positional argument: 'variable' ” 错误。花了 1 个小时才注意到您需要确定发送到插槽的数据类型。 i'm just putting here reminder who is copy pasting the code and struggle with this..我只是在这里提醒谁在复制粘贴代码并为此苦苦挣扎..

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