Qt基础之九:子线程和GUI交互

Posted 草上爬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt基础之九:子线程和GUI交互相关的知识,希望对你有一定的参考价值。

首先要强调的是,子线程是不能直接操作GUI的,关于原因,详见:
C++面试题之为什么不能多线程直接操作GUI状态  
Qt提供了三种方式来实现异步操作GUI
1.postEvent
2.信号和槽
3.InvokeMethod

一.postEvent

[static] void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
:postEvent函数将事件添加到事件队列后立即返回,receiver是该事件的接收者。事件必须分配到堆上,因为事件队列将获得事件的所有权,并在发布后将其删除。在事件发布后访问它是不安全的。
事件按优先级降序排序,即优先级高的事件在优先级低的事件之前排队。优先级可以是任何整数值,即INT_MAX和INT_MIN之间,包括INT_MAX;有关详细信息,请参阅Qt::EventPriority。
许多第三方库通过回调函数的方式将数据传给上层处理,比如将解码后的图像数据交给Qt渲染显示。但回调函数不一定运行在主线程中,因此我们需要将数据封装成事件,然后用postEvent添加到事件列表,接着就可以在主线程重写的event方法中处理该事件了。

struct Frame

    uint8_t *buffer;
    int width;
    int height;
;
// 回调函数.
void FrameNotifyHandler(void *userData, Frame *frame)

    QHMediaPlayerGL *self = (QHMediaPlayerGL *)userData;
    QHFrameEvent::postEvent(self, frame);

// 主线程中处理事件.
bool QHMediaPlayerGL::event(QEvent *e)

    QHFrameEvent *ev = QHFrameEvent::event(e);    
    if(ev)
    
        if(hasPlayMedia())
        
            m_videoBuffer=ev->frame()->buffer;
            m_videoWidth=ev->frame()->width;
            m_videoHeight=ev->frame()->height;
            update();
        

        return true;
    

    return QWidget::event(e);

二.信号和槽

常用于将子线程执行结果更新到GUI。下面的例子中将子线程中的累加值更新到按钮上

class MyThread: public QThread

    Q_OBJECT

public:
    explicit MyThread(QObject *parent = nullptr): QThread(parent)
    

    

    ~MyThread()
    
        requestInterruption();
        quit();
        wait();
    

    void run()
    
        qDebug() <<"worker thread id=" << QThread::currentThreadId();
        static int i = 0;
        while(!isInterruptionRequested())
        
            sleep(1);
            emit update(i++);
        
    

signals:
    void update(int value);
;
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)

    ui->setupUi(this);

    qDebug() <<"main thread id=" << QThread::currentThreadId();

    MyThread *thread = new MyThread(this);
    connect(thread, &MyThread::update, this, [=](int value)
         ui->pushButton->setText(QString::number(value));
    );

    // 处理子线程的finished信号.
    connect(thread, &MyThread::finished, []
        qDebug() << "worker thread exit";
    );

    thread->start();

三.InvokeMethod

关于InvokeMethod详见:
Qt基础之五:使用invokeMethod异步调用函数 

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include <QtConcurrent>

QT_BEGIN_NAMESPACE
namespace Ui  class MainWindow; 
QT_END_NAMESPACE

class MainWindow : public QMainWindow

    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

    bool m_running;
    QFuture<void> m_future;
;
#endif // MAINWINDOW_H

mainwindow.cpp 

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QThread>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , m_running(true)

    ui->setupUi(this);
    qDebug()<<"main thread id="<<QThread::currentThreadId();
    
    m_future = QtConcurrent::run([&]
        static int i = 0;
        qDebug()<<"worker thread id="<<QThread::currentThreadId();
        while(m_running)
        
            QThread::sleep(1);
            QMetaObject::invokeMethod(this, [&]
                ui->pushButton->setText(QString::number(i++));
                qDebug()<<"invokeMethod thread id="<<QThread::currentThreadId();
                
            );
        
    );


MainWindow::~MainWindow()

    m_running = false;
    m_future.waitForFinished();
    
    delete ui;

由于使用了并发模块QtConcurrent,需要在 .pro 中添加: QT += concurrent

以上是关于Qt基础之九:子线程和GUI交互的主要内容,如果未能解决你的问题,请参考以下文章

如何并行运行 Qt GUI 和 Linux 消息队列接收线程?

Python Qt GUI设计:多线程中信号与槽的使用(基础篇—9)

Qt Main-Gui 和其他线程+事件循环

Python Qt GUI设计:QTimer计时器类QThread多线程类和事件处理类(基础篇—8)

QT多线程之---moveToThread用法

qt多个线程调用同一个类怎么处理