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();
}
运行效果


浙公网安备 33010602011771号