0.前言
GUI框架一般只允许UI线程操作界面组件,Qt也是如此。但我们的应用程序一般是多线程的,势必就涉及到UI线程与子线程的交互。
下面介绍常用的UI线程与子线程交互方式,并附上自己的Demo。
1.Qt中几种常见的多线程交互的操作
Qt中提供了一些线程安全的方法来让我们使用:
A.使用信号槽
Qt的信号槽是线程安全的。connect函数的第五个参数ConnectionType默认为Qt::AutoConnection,如果接收者和发送者不在一个线程,则相当于自动使用Qt::QueuedConnection类型,槽函数会在接收者线程执行。
connect(this,&MainWindow::signalDoing,worker,&MyWorker::slotDoing);
B.使用 QMetaObject::invokeMethod
invokeMethod可以线程安全的对目标对象进行操作,如调用目标对象的成员函数等。它也具有一个ConnectionType参数,参照connect。
qDebug()<<"main thread"<<QThread::currentThread();
connect(ui->btnDoB,&QPushButton::clicked,this,[this]{
QtConcurrent::run([=]{
qDebug()<<"run doing"<<QThread::currentThread();
QMetaObject::invokeMethod(this,[=]{ //这个this就是传递进来的mainwindow
qDebug()<<"invoke doing"<<QThread::currentThread();
ui->textEditB->append("invoke test");
});
});
});
C.使用 QApplication::postEvent
自定义事件我没怎么用,但postEvent也是线程安全的操作。
QApplication::postEvent(target,new Event(type)));
当然,要完成多线程中与UI的交互不止上面三种方式,可以参照百度其他人提供的思路。
2.示例代码
运行效果:
代码git链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qt/UiAndSubThread
实现代码长了点,贴主要部分:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDateTime>
#include <QtConcurrentRun>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("QQ交流群:910502689");
//【示例A】通过信号槽
thread=new QThread(this);
//要move到别的线程的话不要传递parent参数
worker=new MyWorker();
//更改该对象及其子对象的线程关联性。如果对象具有父对象,则无法移动。
//事件处理将在targetThread中继续。
worker->moveToThread(thread);
//官方示例里的释放方式
connect(thread,&QThread::finished,worker,&QObject::deleteLater);
//worker的定时器开关
ui->btnTimer->setCheckable(true); //Checkable就能有两种状态-对应定时器开关
connect(ui->btnTimer,&QPushButton::clicked,worker,&MyWorker::slotTimerSwitch);
//worker执行任务
connect(ui->btnDoA,&QPushButton::clicked,this,[this]{
emit signalDoing(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")+" main");
});
connect(this,&MainWindow::signalDoing,worker,&MyWorker::slotDoing);
//worker操作结果
connect(worker,&MyWorker::signalMessage,ui->textEditA,&QTextEdit::append);
//启动线程
thread->start();
//【示例B】通过invokeMethod方法
//(这里我直接用concurrent模块的run函数)
qDebug()<<"main thread"<<QThread::currentThread();
connect(ui->btnDoB,&QPushButton::clicked,this,[this]{
QtConcurrent::run([=]{
qDebug()<<"run doing"<<QThread::currentThread();
//使用QMetaObject::invokeMethod()操作是线程安全的
QMetaObject::invokeMethod(this,[=]{ //这个this就是传递进来的mainwindow
qDebug()<<"invoke doing"<<QThread::currentThread();
ui->textEditB->append(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")+" invoke finished");
});
});
});
//【示例C】通过postEvent
//(需要重写接收者的event()事件处理函数)
connect(ui->btnDoC,&QPushButton::clicked,this,[this]{
QtConcurrent::run([=]{
qDebug()<<"run doing"<<QThread::currentThread();
//postEvent是非阻塞的,sendEvent是阻塞的,postEvent是线程安全的
QApplication::postEvent(this,new MyEvent(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")));
});
});
}
MainWindow::~MainWindow()
{
//退出示例A的线程,注意里面若有死循环要提前break
thread->quit();
thread->wait();
delete ui;
}
bool MainWindow::event(QEvent *event)
{
if(event->type()==MyEvent::eventType){
MyEvent *my_event=dynamic_cast<MyEvent*>(event);
if(my_event){
//通过postevent传递信息
ui->textEditB->append(my_event->message+" event finished");
}
}
return QMainWindow::event(event);
}