前言
在Qt事件循环详解(一)中讲到,可以用QEventLoop来阻塞当前函数并开启事件循环,在UI程序中,我们可以用这种方法将一个异步操作转化成一个同步操作。
异步调用举例
我们来看一个例子,我们向服务器请求服务器时间,异步的方式,我们的写法是这样的:
void Helper::getServerTimeAsync()
{
auto networkManager = new QNetworkAccessManager;
connect(networkManager, &QNetworkAccessManager::finished, [=](QNetworkReply* reply) {
networkManager->deleteLater();
emit serverTime(reply->rawHeader("time").toLongLong());
});
networkManager->get(QNetworkRequest(QUrl("https://10.10.10.1/time")));
}
在上述代码中,我们通过连接QNetworkAccessManager::finished
信号来在未来某个时候发出finished
信号时处理回复数据,然后将得到的时间以信号的方式发出去。
调用者在使用这个接口时,那就是下面这样:
auto helper = new Helper;
connent(helper, &Helper::serverTime, [](time_t time) {
qDebug() << time;
});
helper.getServerTimeAsync();
相当于调用getServerTimeAsync只是发一个获取时间的请求,这个时候并不能立即得到时间,必须在未来某个时候Helper::serverTime
信号触发时才能得到时间。
改成同步调用
现在,我们通过QEventLoop来改成同步调用:
time_t Helper::getServerTimeSync()
{
QNetworkAccessManager networkManager;
QEventLoop eventLoop;
QTimer timer;
connect(&timer, &QTimer::timeout, [&eventLoop] { eventLoop.quit(); });
connect(&networkManager, &QNetworkAccessManager::finished, [&eventLoop](QNetworkReply* reply) {
eventLoop.quit();
});
auto reply = networkManager.get(QNetworkRequest(QUrl("https://10.10.10.1/time")));
timer.start(3000);
eventLoop.exec();
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError)
return 0;
return reply->rawHeader("time").toLongLong();
}
如上代码,创建定时器用于超时处理,超时时直接退出事件循环;QNetworkAccessManager::finished触发时也退出事件循环;最后使用QEventLoop来开启事件循环阻塞当前函数(由于是阻塞调用,那networkManager不需要new,直接局部变量即可);当超时时间到或者finished信号触发时事件循环退出,我们再去从reply里解析时间,最后返回。
可能看上面代码,实现变复杂了好多,但是接口使用起来就简单很多:
qDebug() << helper.getServerTimeSync();
总结
很多异步的操作,比如上面的QNetworkAccessManager的post、get,或者使用QProcess创建子进程处理任务的情况,都可以改成同步调用,但是要注意的时,改成同步调用后,在调用返回之前,函数是被阻塞掉的,修改之前就要考虑阻塞的话会不会影响业务逻辑。