Qt中UI线程与子线程的交互

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交流群:647637553");

    //【示例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);
}

3.参考

参考文档:https://doc.qt.io/qt-5.12/qthread.html

参考博客:https://www.cnblogs.com/lifexy/p/10907901.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章