关于如何实例化和使用的官方文档QThread
可以在这里找到:http://doc.qt.io/qt-5/qthread.html http://doc.qt.io/qt-5/qthread.html
该文档描述了两种基本方法:(1) 工作对象方法和 (2)QThread
子类方法。
我在几篇文章中读到第二种方法不好,所以让我们重点讨论第一种方法。
EDIT:
@ekhumoro 向我指出了以下有趣的文章:https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html。显然,方法(1)和(2)各有其优点:
根据经验:
如果您确实不需要线程中的事件循环,则应该子类化。
如果您需要事件循环并处理线程内的信号和槽,则可能不需要子类化。
由于我确实需要 QApplication 线程和新 QThread 之间进行某种通信(并且我相信信号槽是一种很好的通信方式),因此我将使用工人-对象方法.
1. C++ 中的工作对象方法
我已经复制粘贴了 C++ 代码工人-对象方法(来自Qt5官方文档,参见http://doc.qt.io/qt-5/qthread.html 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_())
NOTE (1):
Initially I had implemented the worker
variable as a local variable in the constructor. I was literally translating the C++ sample to Python, and this variable is also a local variable in the C++ sample.
As you can see in the comment of @pschill, this local variable was garbage collected, and therefore I couldn't get the thread running. After making the change, I get the expected output.
NOTE (2):
I've added this line to know precisely when the workerThread
finishes.
NOTE (3):
Apparently I need to add these two codelines global app
and app.exit()
to the handleResults(..)
slot. Thank you @Matic to point that out!
NOTE (4):
I've discovered (through some documentations) this approach to end the application. The first codeline ends the workerThread
(by killing its event-loop). The second codeline ends the mainThread
(also by killing its event-loop).
NOTE (5):
When running the code in a Windows console, nothing happened (it just hanged). On the advice of @pschill (see his comment below), I added this codeline to make sure that the doWork()
function gets called.
3.我的问题
首先,我想知道我从 C++ 到 Python 的翻译是否正确。请告诉我我在哪里犯了错误(如果您发现任何错误)。
添加代码行global app
and app.exit()
to the handleResults(..)
插槽修复了悬挂问题。但背景到底发生了什么?这些代码线是否会杀死工作线程?还是 QApplication 主线程?
有没有办法杀死工作线程而不杀死主 QApplication 线程?
4.一些答案
1.还是不确定..
2.我相信app.exit()
杀死主线程,主线程又杀死工作线程,因为它是 deamon 类型。我发现工作线程是 deamon 类型,因为我插入了代码行print(threading.current_thread())
in the doWork(..)
功能。它打印了<_DummyThread(Dummy-1, started daemon 9812)>
。当程序退出时,所有守护线程都会自动终止。
3. 是的,我找到办法了!这QThread::quit()
函数是你的朋友。官方文档对此是这么说的:
void QThread::quit()
Tells the thread's event loop to exit with return code 0 (success). Equivalent to calling QThread::exit(0)
.
This function does nothing if the thread does not have an event loop.
[http://doc.qt.io/qt-5/qthread.html#quit] http://doc.qt.io/qt-5/qthread.html#quit%5D
所以我的功能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."))
不幸的是这一行没有打印出来。为什么?
Hereby I provide my current system settings:
> Qt5 (QT_VERSION_STR
= 5.10.1)
> PyQt5 (PYQT_VERSION_STR
= 5.10.1)
> Python 3.6.3
> Windows 10, 64-bit