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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步