Qt实现的生产者消费者模型解决UI刷新问题

实战场景

  • 公司一个老项目, 客户反映程序有偶发性的界面错乱并卡死, 检查代码, 发现有两个网络线程同时在调用刷新界面的接口(且该UI接口是直接刷新, 没有缓冲机制). 这可是操作UI的大忌. 老项目的UI框架就不折腾了, 就把这里加个生产者消费者模型吧:

    • 采用QWaitCondition和QMutex, 控制两个网络线程(NetThread1, NetThread2, 即两个生产者)将需要刷新的数据加入队列queue, 新增一个消费者线程(UpdateUiThread)以wait的方式监听这个queue, queue中有数据, 则取出数据去刷新UI.

模型

graph TD; NetThread1-->Queue; Queue-->UpdateUiThread; NetThread2-->Queue; linkStyle 0 stroke:#aaa,stroke-width:2px; linkStyle 1 stroke:#aaa,stroke-width:2px; linkStyle 2 stroke:#aaa,stroke-width:2px; style NetThread1 fill:#f9f,stroke:#000,stroke-width:1px style UpdateUiThread fill:#f9f,stroke:#000,stroke-width:1px style NetThread2 fill:#f9f,stroke:#000,stroke-width:1px style Queue fill:#f9f,stroke:#000,stroke-width:1px

模型主要代码

  • netthread.cpp
#include "netthread.h"
#include <QDebug>

NetThread::NetThread(QWaitCondition *condition, QMutex *mutex, \
QQueue <QString>* queue, QObject *parent) : QObject(parent),
    condition(condition),mutex(mutex), queue(queue)
{
    //这里用一个定时器简单下模拟网络发送的数据
    timer = new QTimer;
    connect(timer, &QTimer::timeout, this , &NetThread::slotAddQueue);
    timer->start(1000);
}

void NetThread::slotAddQueue()
{
    mutex->lock();
    queue->enqueue(threadId+"发来了数据!");
    condition->wakeAll();
    mutex->unlock();
}

  • updateuithread.cpp
#include "updateuithread.h"
#include <QDebug>

UpdateUiThread::UpdateUiThread(QWaitCondition *condition, QMutex *mutex, \
 QQueue <QString>* queue, QObject *parent) : QObject(parent),
    condition(condition), mutex(mutex), queue(queue)
{

}

void UpdateUiThread::slotUpdateUi()
{
    while(1)
    {
        QString NetData;
        if(queue->count() == 0)
        {
            mutex->lock();
            
            condition->wait(mutex);   /*生产者消费者模型, 主要是要理解wait的机制, wait时, 会阻塞在这里, 且同时也会把锁打开(让生产者(NetThread)可以向queue中插入数据). 
            生产者向queue中插入数据后, 需要调用condition.wakeall()或wakeone(), 当condition收到wake信号时, 会把锁重新锁上(暂时不让生产者向queue中插数据), 并开始执行此处
            后面的代码.*/ 
            while(queue->count() > 0)
            {//实际项目上是进行一系列比较耗时的UI更新操作, 这里用发送信号代替. 
                NetData = queue->dequeue();
                emit signalUpdateUi(NetData);
            }
            mutex->unlock();
        }
    }
}
  • widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
#include <QDebug>
#include "netthread.h"
#include "updateuithread.h"


Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    queue.clear();
    this->netThread_1 = new QThread;
    this->netThread_2 = new QThread;
    this->updateUiThread = new QThread;
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    UpdateUiThread * updatethread = new UpdateUiThread(&condition, &mutex, &queue);
    updatethread->moveToThread(updateUiThread);

    connect(this, &Widget::startUpdateUi, updatethread, &UpdateUiThread::slotUpdateUi);
    connect(updatethread, &UpdateUiThread::signalUpdateUi, this , [=](QString netData){
        ui->plainTextEdit->appendPlainText(netData);
    });
    ui->plainTextEdit->appendPlainText("启动刷新UI线程");
    updateUiThread->start();
    emit startUpdateUi();
}

void Widget::on_pushButton_2_clicked()
{
    NetThread *netthread1 = new NetThread(&condition, &mutex, &queue);
    netthread1->setThreadId("网络线程1");
    netthread1->moveToThread(netThread_1);
    ui->plainTextEdit->appendPlainText("启动网络线程1");
    netThread_1->start();
}

void Widget::on_pushButton_3_clicked()
{
    NetThread *netthread2 = new NetThread(&condition, &mutex, &queue);
    netthread2->setThreadId("网络线程2");
    netthread2->moveToThread(netThread_2);
    ui->plainTextEdit->appendPlainText("启动网络线程2");
    netThread_2->start();
}

运行效果

posted @ 2020-04-04 04:10  技术不支持  阅读(746)  评论(0)    收藏  举报