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

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