需求:
在网络编程中,往往有这种需求,给服务器发送命令1,然后等待服务器应答命令1,然后根据应答的结果判断是否需要发送命令2,命令3.....要求不卡顿界面。
伪代码:
bool Test::send()
{
//发送命令1
int ret = syncSendData(command_1); //向服务器发送command_1
if(ret != 0)
{
return false;
}
// 命令1收到服务器的正确应答,发送命令2
ret = syncSendData(command_2);
if(ret != 0)
{
return false;
}
// 命令2收到服务器的应答,发送命令3
ret = syncSendData(command_3);
if(ret != 0)
{
return false;
}
.
.
.
syncSendData(command_n);
}
QEventLoop解读:
为了实现如上目的,我们想到用QEventLoop的事件循环功能。使用 QEventLoop 的事件循环能使UI界面正常响应,不会出现卡住的现象。但是 QEventLoop 美中不足的地方是,在调用exec()的时候,没有办法过滤掉用户的输入事件,往往导致槽函数重入。来看看函数exec可传入的参数:
QEventLoop::AllEvents:
All events. Note that DeferredDelete events are processed specially. See QObject::deleteLater() for more details.QEventLoop::ExcludeUserInputEvents:
Do not process user input events, such as ButtonPress and KeyPress. Note that the events are not discarded; they will be delivered the next time processEvents() is called without the ExcludeUserInputEvents flag.QEventLoop::ExcludeSocketNotifiers:
Do not process socket notifier events. Note that the events are not discarded; they will be delivered the next time processEvents() is called without the ExcludeSocketNotifiers flag.QEventLoop::WaitForMoreEvents:
Wait for events if no pending events are available.
1)默认参数是QEventLoop::AllEvents,处理所有的事件,容易导致函数重入,用户连续多次点击按钮的话,则响应多次;
2)传QEventLoop::ExcludeUserInputEvents参数来避免函数重入,但请看官方解释:
QEventLoop::ExcludeUserInputEvents
Do not process user input events, such as ButtonPress and KeyPress. Note that the events are not discarded; they will be delivered the next time processEvents() is called without the ExcludeUserInputEvents flag.
看到了没,用户输入的事件并不会被丢弃,而是等到QEventLoop 退出后再响应。按钮依然响应多次,只是响应的时间推迟。
实现过滤用户输入事件:
既然QEventLoop 没有提供在exec()退出前过滤掉用户的输入事件的参数,只能自己实现了,思路:继承QEventLoop,若在UI线程使用,则为qApp安装事件过滤器,在exec()退出之前过滤掉所有的用户的输入事件。
头文件:
#pragma once
#include <QEventLoop>
#include <QEvent>
#include <QThread>
class EventLoopFilter : public QEventLoop
{
Q_OBJECT
public:
EventLoopFilter(QObject* parent = nullptr);
~EventLoopFilter();
int exec();
void deleteLater();
virtual bool eventFilter(QObject* watched, QEvent* event);
private:
bool m_uiThread = false;
};
实现文件:
#include "stdafx.h"
#include "EventLoopFilter.h"
#include <QCoreApplication>
#include "QsLog.h"
EventLoopFilter::EventLoopFilter(QObject* parent) : QEventLoop(parent)
{
}
EventLoopFilter::~EventLoopFilter()
{
}
int EventLoopFilter::exec()
{
if (qApp->thread() == QThread::currentThread()) //UI线程
{
qApp->installEventFilter(this);
m_uiThread = true;
}
return QEventLoop::exec(QEventLoop::AllEvents);
}
void EventLoopFilter::deleteLater()
{
if (m_uiThread)
{
qApp->removeEventFilter(this);
}
return QEventLoop::deleteLater();
}
bool EventLoopFilter::eventFilter(QObject* watched, QEvent* event)
{
//过滤用户输入事件
if (event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease ||
event->type() == QEvent::MouseButtonDblClick ||
event->type() == QEvent::KeyPress ||
event->type() == QEvent::KeyRelease ||
event->type() == QEvent::Wheel ||
event->type() == QEvent::Enter ||
event->type() == QEvent::Leave ||
event->type() == QEvent::HoverEnter ||
event->type() == QEvent::HoverLeave ||
event->type() == QEvent::HoverMove)
{
return true;
}
return false;
}
使用示例:
//伪代码
int Test::syncSendData(const QByteArray& data, int timeout)
{
QPointer<EventLoopFilter> eventLoop = new EventLoopFilter();
//此处将eventLoop对象指针传给命令接收线程
//设置超时
QTimer::singleShot(timeout, [eventLoop, data]()
{
if (eventLoop != nullptr && eventLoop->isRunning())
{
eventLoop->exit(TIME_OUT);
}
});
sendData(data) //tcp发送
ret = eventLoop->exec(); //在命令接收线程接收应答并匹配,匹配到则调用eventLoop对象的退出函数exit
eventLoop->deleteLater();
eventLoop = nullptr;
return ret;
}