Pyqt5 中的 QThreads:这是官方 QThread 文档的 C++ 到 Python 的正确翻译吗?
QThreads in Pyqt5: is this the correct C++ to Python translation of the official QThread docs?
关于如何实例化和使用 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) 各有各的优点:
As a rule of thumb:
If you do not really need an event loop in the thread, you should subclass.
If you need an event loop and handle signals and slots within the thread, you may not need to subclass.
因为我确实需要 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
中的 worker-object 方法
我努力将给定的 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 的评论中看到的那样,这个局部变量被垃圾回收了,因此我无法获取线程 运行ning。进行更改后,我得到了预期的输出。
注 (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()
函数是你的朋友。官方文档是这么说的:
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]
所以我的函数 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 位
您的 Python 示例应用程序需要以某种方式退出,否则它只会在 Controller
对象初始化后停在那里。
最简单的方法是将示例中的 handleResults
函数更改为:
@pyqtSlot(str)
def handleResults(self, param):
print(param)
global app
app.exit()
希望对您有所帮助。
关于如何实例化和使用 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) 各有各的优点:
As a rule of thumb:
If you do not really need an event loop in the thread, you should subclass. If you need an event loop and handle signals and slots within the thread, you may not need to subclass.
因为我确实需要 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
中的 worker-object 方法我努力将给定的 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 的评论中看到的那样,这个局部变量被垃圾回收了,因此我无法获取线程 运行ning。进行更改后,我得到了预期的输出。
注 (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()
函数是你的朋友。官方文档是这么说的:
void
QThread::quit()
Tells the thread's event loop to exit with return code 0 (success). Equivalent to callingQThread::exit(0)
.
This function does nothing if the thread does not have an event loop.
[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 位
您的 Python 示例应用程序需要以某种方式退出,否则它只会在 Controller
对象初始化后停在那里。
最简单的方法是将示例中的 handleResults
函数更改为:
@pyqtSlot(str)
def handleResults(self, param):
print(param)
global app
app.exit()
希望对您有所帮助。