文章目录
- 需求
- QEventLoop
- Demo1- 登录
- Demo2- 延时
- Demo3- 在程序中等待服务器返回
- 理解QT事件循环
- 事件循环抽象
- Qt是事件驱动的
- Qt常见事件
- Qt事件从哪里来
- Qt事件队列
- Qt事件接收
- Qt事件循环
- Qt事件同步和异步分发
- processEvents不阻塞UI
- Qt 事件循环嵌套
需求
- 登录时,等待服务器返回,才能知道下一步结果
- 希望某线程等待100ms,但不会影响UI刷新
- 在程序里等待服务器返回
QEventLoop
At any time, you can create a QEventLoop object and call exec() on it to start a local event loop. From within the event loop, calling exit() will force exec() to return.
- QEventLoop类为我们提供了一种进入和退出一个事件循环的方法。在任何时候,你都可以创建一个QEventLoop实例,然后调用exec()来启动一个事件循环,在这个循环期间,可以调用exit()来强制使exct()返回。
- exec( )之后的代码不会执行,直至从exec( )跳出。
Demo1- 登录
bool login(const QString &userName, const QString &passwdHash, const QString &slat)
{
//声明本地EventLoop
QEventLoop loop;
bool result = false;
//先连接好信号
connect(&network, &Network::result, [&](bool r, const QString &info){
result = r;
qDebug() << info;
//槽中退出事件循环
loop.quit();
});
//发起登录请求
sendLoginRequest(userName, passwdHash, slat);
//启动事件循环。阻塞当前函数调用,但是事件循环还能运行。
//这里不会再往下运行,直到前面的槽中,调用loop.quit之后,才会继续往下走
loop.exec();
//返回result。loop退出之前,result中的值已经被更新了。
return result;
}
Demo2- 延时
1 QEventLoop loop;
2 QTimer::singleShot(100, &loop, SLOT(quit()));
3 loop.exec();
主线程延时Demo
Demo3- 在程序中等待服务器返回
- 增加事件循环
//以上省略了部分代码,这是使用HTTP中的post来发送文件到服务器
QNetworkReply *postReply = m_pNetManager->post(request, qbt); //post方式到本地服务器
connect(postReply, SIGNAL(finished()), this, SLOT(postFileReplyFinished())); //成功后会有返回响应
loop->exec(); //设置等待,若文件成功发送,则退出等待
- 在服务器接收中退出事件循环
void Widget::postFileReplyFinished()
{
QNetworkReply* reply = (QNetworkReply*)sender();
QByteArray replyData = reply->readAll();
//转为JSon格式,便于提取字段数据
QJsonDocument jsonDoc= QJsonDocument::fromJson(replyData);
if(!jsonDoc.isNull())
{
QJsonObject jsonObj = jsonDoc.object(); //转换格式
if(jsonObj.contains("status"))
{
loop->exit(); //loop退出等待
}
}
}
在程序中等待服务器返回 Demo
理解QT事件循环
事件循环抽象
function loop() {
initialize();
bool shouldQuit = false;
while(false == shouldQuit)
{
var message = get_next_message();
process_message(message);
if (message == QUIT)
{
shouldQuit = true;
}
}
}
Qt是事件驱动的
- Qt将系统产生的信号(软件中断)转换成Qt事件,并且将事件封装成类,所有的事件类都是由QEvent派生的,事件的产生和处理就是Qt程序的主轴,且伴随着整个程序的运行周期。因此我们说,Qt是事件驱动的。
Qt常见事件
- 鼠标事件(QMouseEvent)
- 键盘事件(QKeyEvent)
- 绘制事件(QPaintEvent)
- 窗口尺寸改变(QResizeEvent)
- 滚动事件(QScrollEvent)
- 控件显示(QShowEvent)
- 控件隐藏(QHideEvent)
- 定时器事件(QTimerEvent)
Qt事件从哪里来
- Qt的官方手册说,事件有两个来源:程序外部和程序内部,多数情况下来自操作系统并且通过spontaneous()函数返回true来获知事件来自于程序外部,当spontaneous()返回false时说明事件来自于程序内部。
Qt事件队列
Qt事件(外部或内部)来了之后,进入事件队列,然后由派发器派发。
Qt事件接收
- QObject!它是所有Qt类的基类!是Qt对象模型的核心!QObject类的三大核心功能其中之一就是:事件处理。QObject通过event()函数调用获取事件。所有的需要处理事件的类都必须继承自Qobject,可以通过重定义event()函数实现自定义事件处理或者将事件交给父类。
Qt事件接收
Qt事件循环
- 事件是一个类对象具有特定的类型,事件多数情况下是被分发到一个队列中(事件队列),当队列中有事件时就不停的将队列中的事件发送给QObject对象,当队列为空时就阻塞地等待事件,这个过程就是事件循环!
- QCoreApplication::exec()开启了这种循环,一直到QCoreApplication::exit()被调用才终止,所以说事件循环是伴随着Qt程序的整个运行周期!
Qt事件同步和异步分发
- sendEvent
sendEvent发出的事件会立即被处理,也就是“同步”执行。
- postEvent
postEvent发送的事件会被加入事件队列,在下一轮事件循环时才处理,也就是“异步”执行。
- sendPostedEvents
还有一个特殊的sendPostedEvents,是将已经加入队列中的准备异步执行的事件立即同步执行。
- 事件和信号对比
processEvents不阻塞UI
- 60 FPS, 1s 60帧, 1000ms/60 , 约16ms刷新一次
- 复杂计算> 16ms
- 没有完成计算,不会退出, UI卡住
- 使用postEvents
//耗时操作
someWork1()
//适当的位置,插入一个processEvents,保证事件循环被处理
QCoreApplication::processEvents();
//耗时操作
someWork2()
Qt 事件循环嵌套
事件循环是可以嵌套的,当在子事件循环中的时候,父事件循环中的事件实际上处于中断状态,当子循环跳出exec之后才可以执行父循环中的事件。当然,这不代表在执行子循环的时候,类似父循环中的界面响应会被中断,因为往往子循环中也会有父循环的大部分事件,执行QMessageBox::exec(),QEventLoop::exec()的时候,虽然这些exec()打断了main()中的QApplication::exec(),但是由于GUI界面的响应已经被包含到子循环中了,所以GUI界面依然能够得到响应。
子层事件循环具有父层事件循环的所有功能,所以当在主线程中启动各种exec()(比如QEventLoop::exec())时,虽然会打断main函数中的QApplication::exec(),但是Gui界面还是可以正常响应,不会出现卡住的现象。这与用while来循环是不一样的。
如果某个子事件循环仍然有效,但其父循环被强制跳出,此时父循环不会立即执行跳出,而是等待子事件循环跳出后,父循环才会跳出