问题描述
我试图了解在 QWidget
和 Qt 并发方面允许和不允许的内容。我创建了一个 Widget
,它有一个 slow_function
,我正在考虑三种情况:
- 在 GUI 线程上运行
slow_function
。这会导致预期的行为;在等待函数返回时,GUI 变得无响应。 - 使用
QtConcurrent::run(this,&Widget::slow_function)
。我很惊讶地看到这并没有阻止 GUI。我已经确认我的实例的线程关联仍然是 GUI 线程,但是,该函数似乎在单独的线程上执行。是否允许这种方法,这是预期的行为(文档链接真的很有帮助)?如果我可以保证 slow_function 是线程安全的,那么这种方法是否安全? - 创建
QThread
的子类,其中包含指向我的小部件的指针。覆盖run
方法以调用slow_function
。行为与案例 2 相同。这也令人惊讶,因为线程关联仍然是 GUI 线程(此外,我们甚至不允许在movetoThread
上使用QWidget
)。为什么这是在单独的线程上运行?movetoThread
是否意味着只有当我们有兴趣通过从另一个线程发送的信号来调用插槽时才有用?
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QDebug>
#include <QPushButton>
#include <QLayout>
#include <windows.h>
#include <QtConcurrent/QtConcurrent>
#include <QThread>
#include <QApplication>
class Widget;
class Thread: public QThread
{
public:
Thread(Widget* widget)
: m_widget(widget){}
protected:
void run() override;
private:
Widget* m_widget;
};
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
: QWidget(parent),m_thread(this){
auto layout = new QVBoxLayout(this);
auto button = new QPushButton("Case 1: Run on gui thread");
auto button2 = new QPushButton("Case 2: Run with qtconcurrent");
auto button3 = new QPushButton("Case 3: Run with qthread");
connect(button,&QPushButton::clicked,this,&Widget::slow_function);
connect(button2,&Widget::use_concurrent);
connect(button3,&Widget::use_qthread);
layout->addWidget(button);
layout->addWidget(button2);
layout->addWidget(button3);
}
~Widget()
{
m_thread.quit();
m_thread.wait();
}
public slots:
void slow_function()
{
qDebug() << "Starting";
auto gui_thread = QApplication::instance()->thread();
auto this_thread = thread();
qDebug() << "Thread affinity is" << (gui_thread == this_thread ? "gui_thread" : "non_gui_thread");
Sleep(5000);
qDebug() << "Finished";
}
void use_concurrent()
{
QtConcurrent::run(this,&Widget::slow_function);
}
void use_qthread()
{
m_thread.start();
}
private:
Thread m_thread;
};
#endif // WIDGET_H
和 main.cpp 文件:
#include "widget.h"
#include <QApplication>
void Thread::run()
{
m_widget->slow_function();
}
int main(int argc,char *argv[])
{
QApplication a(argc,argv);
Widget w;
w.show();
return a.exec();
}
解决方法
你不应该在非主线程中做任何 UI 的事情。用户界面意味着
- 小部件交互
- 模型
在 GUI 线程上运行 slow_function
这是不允许的,任何东西都不应该阻塞 GUI 线程。
如果我能保证slow_function是线程安全的,那么这种方法安全吗?
在不同的线程中运行慢函数是可以的。但是...
- 当用户关闭应用程序但函数仍然执行时会发生什么?
我已经这样做了,但我更喜欢将它封装在一个单独的类中。
创建一个 QThread 的子类,它包含一个指向我的小部件的指针。
如果子类是线程,您应该只继承 QThread。比如,将一个动物类子类化会给你一个动物,而不是四条腿的椅子。
moveToThread 是否意味着只有当我们有兴趣通过从另一个线程发送的信号调用插槽时才有用?
moveToThread 改变对象的亲和性。因此,槽总是在正确的线程中执行,当您调用 invokeMethod 时,该方法将在正确的线程中执行。当一个事件被传递时,事件处理程序总是在适当的线程中被调用。