有关如何实例化和使用QThread
的官方文档,请访问:
http://doc.qt.io/qt-5/qthread.html
该文档描述了两种基本方法:(1)工作者 - 对象方法和(2)QThread
子类方法。
我在几篇文章中读到第二种方法不好,所以让我们专注于第一个。
修改
@ekhumoro向我指出了以下有趣的文章:https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html
。显然,两种方法(1)和(2)都有各自的优点:
根据经验:
如果您不需要线程中的事件循环,则应该是子类。 如果你需要一个事件循环并处理线程中的信号和插槽,你可能不需要子类化。
因为我需要在QApplication线程和新的QThread之间进行某种通信(我相信信号槽是一种很好的通信方式),我将使用 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 &);
};
我努力将给定的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()
函数。
首先,我想知道我从C ++到Python的翻译是否正确。请告诉我出错的地方(如果发现任何错误)。
将代码行global app
和app.exit()
添加到handleResults(..)
广告位可修复悬挂问题。但是背景究竟发生了什么?这些代码行是否会杀死工作线程?还是主要的QApplication线程?
有没有办法在不杀死主QApplication线程的情况下终止工作线程?
1。还是不确定..
2.我相信app.exit()
会杀死主线程,而主线程又会杀死工作线程,因为它属于 deamon 类型。我发现工作线程属于 deamon 类型,因为我在print(threading.current_thread())
函数中插入了代码行doWork(..)
。它打印了<_DummyThread(Dummy-1, started daemon 9812)>
。程序退出时,任何守护程序线程都会自动终止。
是的,我找到了办法! 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."))
不幸的是这行不会打印出来。为什么呢?
特此提供我当前的系统设置:
&GT; Qt5(QT_VERSION_STR
= 5.10.1)
&GT; PyQt5(PYQT_VERSION_STR
= 5.10.1)
&GT; Python 3.6.3
&GT; Windows 10,64位
子>
答案 0 :(得分:1)
你的Python示例应用程序需要以某种方式退出,否则它只是在Controller
对象初始化之后就位于那里。
最简单的方法是将示例中的handleResults
函数更改为:
@pyqtSlot(str)
def handleResults(self, param):
print(param)
global app
app.exit()
希望它有所帮助。