用我们的决心、信心和毅力来培植我|

3的4次方

园龄:2年1个月粉丝:5关注:89

📂Qt
🔖qt
2023-06-25 19:17阅读: 553评论: 0推荐: 0

QT为什么只能由主线程操作UI

QT为什么只能由主线程操作UI

背景

QT中,每次非UI线程去更新UI都要通过信号槽去刷。其实不光是QT,很多GUI程序框架都是这么规定的。比如在C#中上位机主要靠主线程刷新UI,当其他线程操作控件时,必须通过委托的方式。委托实质就是一个函数。

主线程:主线程是唯一允许创建QApplication或者QCoreApplication对象的,并且调用exec()启动事件循环的线程。

原因

直接原因:UI 线程负责和用户交互,因此不能长时间阻塞。因此耗时任务必须开启一个后台线程来完成。

根本原因

  • 多线程操作一个UI,很容易导致或者极其容易导致涉及操作了UI的线程间的反向加锁和死锁问题。
  • 刷新UI,得走显卡吧,得刷显存吧,得排队吧,得阻塞吧,涉及到IO还得中断的事件,你敢并发?你以为加锁就ok了?锁能锁住线程,锁不住IO

参考:为什么大多数程序子线程都不能刷新UI? - 知乎 (zhihu.com)

非UI线程中刷新UI

在非 UI 线程中更新 UI (例如改变 QLabel 的文本) 应该使用 信号槽 或者 QMetaObject::invokeMethod(),不要直接调用 widget 的函数,例如在非 UI 线程中直接调用 QLabel::setText(text) 就有可能让程序崩溃。

通过信号槽的方式,由于更新请求和更新动作的完成地在不同的线程中,信号的槽执行会在UI线程的事件循环中出发,避免了非UI线程直接操作UI。

示例

main.cpp

#include "Widget.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

XThread 类

#ifndef XTHREAD_H
#define XTHREAD_H
#include <QThread>
class XThread : public QThread {
Q_OBJECT
public:
XThread();
protected:
void run() Q_DECL_OVERRIDE;
signals:
void currentTime(const QString &time);
};
#endif // XTHREAD_H
#include "XThread.h"
#include <QDateTime>
#include <QDebug>
XThread::XThread() {
}
void XThread::run() {
qDebug() << "XThread: " << QThread::currentThread();
while (true) {
Q_EMIT currentTime(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"));
QThread::msleep(1000);
}
}

Widget 类

#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class XThread;
class QLabel;
class Widget : public QWidget {
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
Q_INVOKABLE void showTime(const QString &time);
private:
XThread *thread;
QLabel *timeLabel;
};
#endif // WIDGET_H
#include "Widget.h"
#include "XThread.h"
#include <QDebug>
#include <QMetaObject>
#include <QLabel>
#include <QHBoxLayout>
Widget::Widget(QWidget *parent) : QWidget(parent) {
// 界面布局
timeLabel = new QLabel("");
QHBoxLayout *hl = new QHBoxLayout();
hl->addWidget(new QLabel("线程里的信号触发修改:"));
hl->addWidget(timeLabel);
this->setLayout(hl);
// 创建启动线程
thread = new XThread();
thread->start();
// 事件处理 1
connect(thread, &XThread::currentTime, [this](const QString &time) {
qDebug() << "connect: " << QThread::currentThread(); // 当前环境的上下文属于线程 XThread
this->showTime(time); // Error: 有时候没问题,有时候会有警告,有的时候程序直接退出,所以不要这么做,相当于在 XThread 中直接调用
QMetaObject::invokeMethod(this, "showTime", Q_ARG(QString, time)); // OK: 一个线程调中用另外一个线程中函数的正确姿势
});
// 事件处理 2
connect(thread, &XThread::currentTime, this, &Widget::showTime); // OK: 使用信号槽
}
Widget::~Widget() {
}
void Widget::showTime(const QString &time) {
qDebug() << "showTime: " << QThread::currentThread(); // 使用 invokeMethod() 调用时属于 Ui 线程
this->timeLabel->setText(time);
}

输出,发现有 2 个线程:

  • 0x7fdcf660e040 是 XThread
  • 0x7fdcf65001e0 是 Ui 线程
XThread: XThread(0x7fdcf660e040)
connect: XThread(0x7fdcf660e040)
showTime: XThread(0x7fdcf660e040)
connect: XThread(0x7fdcf660e040)
showTime: XThread(0x7fdcf660e040)
showTime: QThread(0x7fdcf65001e0)
showTime: QThread(0x7fdcf65001e0)
showTime: QThread(0x7fdcf65001e0)
showTime: QThread(0x7fdcf65001e0)
connect: XThread(0x7fdcf660e040)
showTime: XThread(0x7fdcf660e040)
showTime: QThread(0x7fdcf65001e0)
showTime: QThread(0x7fdcf65001e0)
connect: XThread(0x7fdcf660e040)
showTime: XThread(0x7fdcf660e040)
showTime: QThread(0x7fdcf65001e0)

结果

// 在 XThread 线程上下文里调用
this->showTime(time);
// 在 Ui 线程上下文里调用
QMetaObject::invokeMethod(this, "showTime", Q_ARG(QString, time));
connect(thread, &XThread::currentTime, this, &Widget::showTime);

本文作者:3的4次方

本文链接:https://www.cnblogs.com/3to4/p/17503768.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   3的4次方  阅读(553)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起