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);
}