【发布时间】:2018-11-05 00:09:33
【问题描述】:
关于如何实例化和使用QThread 的官方文档可以在这里找到:
http://doc.qt.io/qt-5/qthread.html
文档描述了两种基本方法:(1) worker-object 方法和 (2)QThread 子类方法。
我在几篇文章中读到第二种方法不好,所以让我们关注第一个。
编辑:
@ekhumoro 为我指出了以下有趣的文章:https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html
.显然,方法 (1) 和 (2) 各有各的优点:
根据经验:
如果你真的不需要线程中的事件循环,你应该子类化。 如果您需要一个事件循环并在线程中处理信号和槽,您可能不需要子类化。
由于我确实需要在 QApplication 线程和新 QThread 之间进行某种通信(而且我相信信号槽是一种很好的通信方式),我将使用 worker-object 方法 .
1。 C++ 中的工作对象方法
我已经复制粘贴了 worker-object 方法的 C++ 代码(来自官方 Qt5 文档,请参阅 http://doc.qt.io/qt-5/qthread.html):
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString ¶meter) {
QString result;
/* ... here is the expensive or blocking operation ... */
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, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
2。 Python 中的工作对象方法
我努力将给定的 C++ 代码翻译成 Python。如果你安装了 Python 3.6 和 PyQt5,你可以简单地复制粘贴这段代码并在你的机器上运行它。它应该可以工作。
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Worker(QObject):
resultReady = pyqtSignal(str)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@pyqtSlot(str)
def doWork(self, param):
result = "hello world"
print("foo bar")
# ...here is the expensive or blocking operation... #
self.resultReady.emit(result)
class Controller(QObject):
operate = pyqtSignal(str)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 1. Create 'workerThread' and 'worker' objects
# ----------------------------------------------
self.workerThread = QThread()
self.worker = Worker() # <- SEE NOTE(1)
self.worker.moveToThread(self.workerThread)
# 2. Connect all relevant signals
# --------------------------------
self.workerThread.finished.connect(self.worker.deleteLater)
self.workerThread.finished.connect(lambda: print("workerThread finished.")) # <- SEE NOTE(2)
self.operate.connect(self.worker.doWork)
self.worker.resultReady.connect(self.handleResults)
# 3. Start the thread
# --------------------
self.workerThread.start()
def __del__(self):
self.workerThread.quit()
self.workerThread.wait()
@pyqtSlot(str)
def handleResults(self, param):
print(param)
# One way to end application
# ---------------------------
# global app # <- SEE
# app.exit() # NOTE(3)
# Another way to end application
# -------------------------------
self.workerThread.quit() # <- SEE NOTE(4)
self.thread().quit()
if __name__ == '__main__':
app = QCoreApplication([])
controller = Controller()
controller.operate.emit("foo") # <- SEE NOTE(5)
sys.exit(app.exec_())
注意(1):
最初,我在构造函数中将worker 变量实现为局部变量。我实际上是在将 C++ 示例翻译成 Python,而这个变量也是 C++ 示例中的一个局部变量。
正如您在@pschill 的评论中看到的那样,这个局部变量被垃圾收集了,因此我无法让线程运行。进行更改后,我得到了预期的输出。
注意(2):
我已添加此行以准确了解 workerThread 何时结束。
注意(3):
显然我需要将这两个代码行 global app 和 app.exit() 添加到 handleResults(..) 插槽。谢谢@Matic 指出这一点!
注意(4):
我已经(通过一些文档)发现了这种结束应用程序的方法。第一个代码行结束了workerThread(通过终止其事件循环)。第二条代码行结束了mainThread(也通过终止其事件循环)。
注意(5):
在 Windows 控制台中运行代码时,什么都没有发生(它只是挂起)。根据@pschill 的建议(请参阅下面的评论),我添加了此代码行以确保调用doWork() 函数。
3。我的问题
首先,我想知道我从 C++ 到 Python 的翻译是否正确。请告诉我我在哪里犯了错误(如果你发现任何错误)。
将代码行
global app和app.exit()添加到handleResults(..)插槽可解决挂起问题。但是背景究竟发生了什么?这些代码行是否会杀死工作线程?还是主 QApplication 线程?有没有办法在不杀死主 QApplication 线程的情况下杀死工作线程?
4。一些答案
1。还是不确定..
2.我认为app.exit()杀死了主线程,而主线程又杀死了工作线程,因为它是deamon类型的。我发现工作线程是 deamon 类型的,因为我在doWork(..) 函数中插入了代码行print(threading.current_thread())。它打印了<_DummyThread(Dummy-1, started daemon 9812)>。当程序退出时,所有守护线程都会自动终止。
3. 是的,我找到了方法! QThread::quit() 函数是你的朋友。官方文档是这样说的:
无效
QThread::quit()
告诉线程的事件循环以返回码 0(成功)退出。等效于调用QThread::exit(0)。
如果线程没有事件循环,则此函数不执行任何操作。
[http://doc.qt.io/qt-5/qthread.html#quit]
所以我的函数handleResults(..) 现在看起来像这样:
@pyqtSlot(str)
def handleResults(self, param):
print(param)
self.workerThread.quit() # Kill the worker thread
self.thread().quit() # Kill the main thread
我已经通过在Controller(..) 的构造函数中插入这一行来检查工作线程的终止:
self.workerThread.finished.connect(lambda: print("workerThread finished."))
我确实按预期打印了该行。我也尝试以类似的方式检查主线程的终止:
self.thread().finished.connect(lambda: print("mainThread finished."))
不幸的是,这条线没有打印出来。为什么?
特此提供我当前的系统设置:
> Qt5 (QT_VERSION_STR = 5.10.1)
> PyQt5 (PYQT_VERSION_STR = 5.10.1)
> Python 3.6.3
> Windows 10,64 位
【问题讨论】:
-
目前,没有人调用
doWork函数,这就是您看不到任何输出的原因。例如,您可以发出连接到doWork的operate信号。与此无关,您的应用程序挂起,因为您从未停止线程,因此它的事件循环继续运行。 -
所有声称第二种方法在某种程度上“不好”的文章都是错误的。并且应该被忽略。当前的 qt 文档正确地包含了这两种方法,因为它们为稍微不同的问题提供了有效的解决方案。如果您想正确理解这些问题,请阅读以下内容:QThread: You were not doing so wrong。
-
@K.Mulier
worker只是__init__中的一个局部变量,它会在函数结束时被删除。您可以通过添加一个调用print("deleted worker")的__del__函数来对此进行测试。如果将对象设为控制器的成员(使用self.worker而不是worker),则该对象将保持活动状态。试试看,hello world会在发出operate信号后出现:) -
@K.Mulier To (1):C++ 有手动管理:使用
new构造的对象一直存在,直到使用delete销毁,在Qt 的情况下,QObject 父级删除其子级.在这里,当调用moveToThread时,QThread 成为工作线程的父级,因此工作线程保持活动状态。 Python 不同:一个对象在不再使用时被销毁(引用计数),Qt 父子所有权在 Python 中没有影响。您必须通过使对象成为类成员来自己保持对象存活。 -
@K.Mulier To (2):正如我所说,QThread 永远不会停止,它的事件循环会继续运行。您可以通过多次发出
operate信号来测试这一点:doWork函数将被多次调用。您必须停止线程,例如,通过在您的__main__中调用controller.workerThread.quit()。
标签: python pyqt qt5 pyqt5 qthread