QTimer 插槽中的 QEventLoop
我在同一个线程中有多个QTimer,在连接到QTimer的插槽中,我使用QEventLoop进行同步,就像http请求一样,但我发现不同的QTimer以不同的顺序启动时可能会互相影响。
这是我的简单测试代码片段:
情况1
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTimer t1;
QTimer t2;
QObject::connect(&t1, &QTimer::timeout, []()->void{
qDebug() << QDateTime::currentDateTimeUtc() << "T1...";
QEventLoop loop;
// t1 slot run time 3000ms
QTimer::singleShot(3000, &loop, SLOT(quit()));
loop.exec();
});
QObject::connect(&t2, &QTimer::timeout, []()->void{
qDebug() << QDateTime::currentDateTimeUtc() << "T2...";
QEventLoop loop;
// t2 slot run time 100ms
QTimer::singleShot(100, &loop, SLOT(quit()));
loop.exec();
});
// interval 1000ms, start t1 first and then t2
t1.start(1000);
t2.start(1000);
return a.exec();
}
输出:
QDateTime(2022-03-21 14:00:51.014 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:00:51.016 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:00:54.025 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:00:54.027 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:00:58.018 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:00:58.019 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:01:01.014 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:01:01.015 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:01:04.016 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:01:04.016 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:01:07.019 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:01:07.020 UTC Qt::TimeSpec(UTC)) T1...
从输出中可以看到t2受到t1的影响,实际间隔约为3000~4000ms,对于t1和t2来说,我认为t2不应该受到影响
情况 2
只需更改 QTimer 启动顺序,但情况会变得不同
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTimer t1;
QTimer t2;
QObject::connect(&t1, &QTimer::timeout, []()->void{
qDebug() << QDateTime::currentDateTimeUtc() << "T1...";
QEventLoop loop;
// t1 slot run time 3000ms
QTimer::singleShot(3000, &loop, SLOT(quit()));
loop.exec();
});
QObject::connect(&t2, &QTimer::timeout, []()->void{
qDebug() << QDateTime::currentDateTimeUtc() << "T2...";
QEventLoop loop;
// t2 slot run time 100ms
QTimer::singleShot(100, &loop, SLOT(quit()));
loop.exec();
});
// interval 1000ms, start t2 first and then t1
t2.start(1000);
t1.start(1000);
return a.exec();
}
输出:
QDateTime(2022-03-21 14:04:53.653 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:04:53.656 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:54.659 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:55.655 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:56.656 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:04:56.656 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:57.664 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:58.657 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:59.665 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:04:59.667 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:00.660 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:01.662 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:02.652 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:03.650 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:05:03.653 UTC Qt::TimeSpec(UTC)) T2...
现在 t1 不再影响 t2,实际间隔为t1约为3000~4000ms,t2为1000ms。
我真的很困惑为什么不同的启动顺序会有不同的结果。有人能帮我解释一下吗?
平台:Qt 5.5.0 MinGW 32bit,Windows
IDE:Qt Creator 3.4.2
您可以直接在 main() 中运行我的测试代码并检查测试结果。
谢谢。
编辑1:
我使用QEventLoop进行RESTFul API的同步http请求,如下所示:
// m_http is QNetworkAccessManager
QNetworkReply *reply = m_http->get(request);
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
在定时器槽中,只有一个同步http请求,以定期轮询数据。 通常情况下请求会很快完成,但有时如果发生错误,会花费超过1000ms。 我的应用程序需要同时与多个服务器通信,每个连接都有一个计时器来进行轮询,所有连接实例都在同一个线程中,因此所有计时器共享相同的事件循环。 我的测试是模拟请求的简单方法,使用 QTimer::singleShot 作为请求的时间成本。 有没有更好的方法在QT中同步http请求?使用 QEventLoop 可能不是最好的。
I have multiple QTimer in the same thread, in the slot connected to the QTimer, I use QEventLoop for synchronization, like http requests, but I found different QTimers may affect each other when they are started in different orders.
Here is my simple test code snippet:
Case 1
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTimer t1;
QTimer t2;
QObject::connect(&t1, &QTimer::timeout, []()->void{
qDebug() << QDateTime::currentDateTimeUtc() << "T1...";
QEventLoop loop;
// t1 slot run time 3000ms
QTimer::singleShot(3000, &loop, SLOT(quit()));
loop.exec();
});
QObject::connect(&t2, &QTimer::timeout, []()->void{
qDebug() << QDateTime::currentDateTimeUtc() << "T2...";
QEventLoop loop;
// t2 slot run time 100ms
QTimer::singleShot(100, &loop, SLOT(quit()));
loop.exec();
});
// interval 1000ms, start t1 first and then t2
t1.start(1000);
t2.start(1000);
return a.exec();
}
Output:
QDateTime(2022-03-21 14:00:51.014 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:00:51.016 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:00:54.025 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:00:54.027 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:00:58.018 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:00:58.019 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:01:01.014 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:01:01.015 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:01:04.016 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:01:04.016 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:01:07.019 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:01:07.020 UTC Qt::TimeSpec(UTC)) T1...
From the output, you can see t2 is affected by t1, the actual interval is about 3000~4000ms, both for t1 and t2, I think t2 should not be affected
Case 2
Just change the QTimer start order, but things will become different
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTimer t1;
QTimer t2;
QObject::connect(&t1, &QTimer::timeout, []()->void{
qDebug() << QDateTime::currentDateTimeUtc() << "T1...";
QEventLoop loop;
// t1 slot run time 3000ms
QTimer::singleShot(3000, &loop, SLOT(quit()));
loop.exec();
});
QObject::connect(&t2, &QTimer::timeout, []()->void{
qDebug() << QDateTime::currentDateTimeUtc() << "T2...";
QEventLoop loop;
// t2 slot run time 100ms
QTimer::singleShot(100, &loop, SLOT(quit()));
loop.exec();
});
// interval 1000ms, start t2 first and then t1
t2.start(1000);
t1.start(1000);
return a.exec();
}
Output:
QDateTime(2022-03-21 14:04:53.653 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:04:53.656 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:54.659 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:55.655 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:56.656 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:04:56.656 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:57.664 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:58.657 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:59.665 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:04:59.667 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:00.660 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:01.662 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:02.652 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:03.650 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:05:03.653 UTC Qt::TimeSpec(UTC)) T2...
Now t1 does not affect t2 any more, actual interval for t1 is about 3000~4000ms, t2 is 1000ms.
I am really confused why different start order will have different results. Could anyone help me to explain this?
Platform: Qt 5.5.0 MinGW 32bit, Windows
IDE: Qt Creator 3.4.2
You can simply run my test code directly in main() and check the test results.
Thanks.
Edit 1:
I am using QEventLoop for sync http request of RESTFul API, like this:
// m_http is QNetworkAccessManager
QNetworkReply *reply = m_http->get(request);
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
In timer slot, there is only a sync http request, to poll data periodly.
Normally the request will be finished very fast, but sometimes it will cost more than 1000ms if error occurs.
My application need to communicate with multiple server at the same time, and each connection have a timer to do the polling, all connection instances are in the same thread, so all timers share the same eventloop.
My tests is a simple way to simulate the request, using QTimer::singleShot as the time cost by request.
Is there any better way to do sync http request in QT? Using QEventLoop may be not the best.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
首先,检查您的
QTimer::timerType()
:默认值为Qt::CoarseTimer
,精度为 5%。您可能需要Qt::PreciseTimer
。其次,主 Qt 事件循环将序列化所有事件。不管他们是什么。这是一个独特的管道(顺便说一句,它很容易成为应用程序的瓶颈)。它还有一个坏习惯,就是在处理某些事件之前将它们“打包”在一起(例如鼠标事件)。
最后,
QTimer::singleShot
也是一个真正的计时器,您可以在插槽内启动一个计时器 - 基本上,它是在事件循环上下文中执行的,您可以在其中手动调用事件循环处理,在两个不同QEventLoop
对象...说实话,你的计时器做奇怪的事情并不奇怪。您能准确地说出您需要用这些计时器做什么吗?因为你的
QEventLoop
技巧所做的唯一事情就是在请求的时间段内阻塞/扰乱计时器,即T1为3000毫秒,T2为100毫秒,这显然会扰乱你的基本1000毫秒周期 通常,您为QNetworkAccessManager
连接接收信号 (
finished(QNetworkReply*)
),但它会是readyRead()
forQIODevice
) 到您的插槽,并且您使用sender()
允许单个插槽同时处理多个对象。您无需手动同步任何内容,而是让 Qt 为您处理 - 这是使用框架的好处之一。那么,“回复接收”就这样完成了。至于发送请求,由您决定。您可以使用各种解决方案:
QTimer
发送它们,而不会影响您的机器和服务器。请求被尽快推入缓冲区。moveToThread()
才能使其正常工作。我更喜欢解决方案 #2,为每个目标服务器提供一个队列和一个特定的计时器 - 这将允许非常基本的 QoS,并避免对一台服务器的大量请求禁止使用其他服务器。在此之前,您可以使用调度程序功能,该功能将通过关联服务器名称(+协议,如果需要)及其特定队列和计时器的映射自动选择要使用的正确队列。
当你不做任何事情,既没有请求(它们都已发送)并且你正在等待回复时,你让主 Qt 事件循环完成它的工作:避免阻塞消息泵 - 它会产生 ”应用程序没有响应”消息。此外,它还可以减少 CPU 负载、获取应用程序的所有鼠标事件以及刷新进度条等 GUI 元素。
First, check your
QTimer::timerType()
: the default isQt::CoarseTimer
, which has a 5% precision. You may needQt::PreciseTimer
instead.Second, the main Qt event loop will serialize ALL events. Whatever they are. It's an unique pipeline (who can easily become your application's bottleneck, BTW). It also have the bad habit to "pack" some events together (like mouse events) before processing them.
Finally, a
QTimer::singleShot
is also a real timer, and you launch one inside a slot - which is, basically, executed within event loop context, where you call manually an event loop processing, on two distinctQEventLoop
objects...Honestly, it's not very surprising that your timers do strange things. Could you precise exactly what you need to do with these timers? Because the only thing that your
QEventLoop
trick do is to block/mess the timer during the requested period, i.e. 3000 ms for T1 and 100 ms for T2, which will obviously mess with the basic 1000 ms period you requested for each...Normally, you connect the reception signal (
finished(QNetworkReply*)
forQNetworkAccessManager
, but it would bereadyRead()
forQIODevice
) to your slots, and you usesender()
to allow a single slot to process various objects simultaneously. You don't synchronize manually anything, you let Qt handles that for you - that's one of the benefits of using a framework. So, the "reply reception" is done this way.For sending requests, it's up to you. You can use various solutions:
QTimer
to send them without hammering both your machine and the servers. Requests are pushed into the buffer ASAP.moveToThread()
to get this to work properly.I would prefer the solution #2, with a queue and a specific timer for each targetted server - this will allow a very basic QoS and will avoid that a huge number of requests for one server forbids other servers to be used. You can use, before that, a dispatcher function that would automatically choose the correct queue to use through a map associating a server name (+protocol if needed) and its specific queue and timer.
And when you don't do anything, neither requests (they are all sent) and you're waiting for replies, you let the main Qt event loop do its job: avoiding blocking the message pump - it would produce the "The application is not responding" message. Also, it allows to reduce the CPU load, to get all mouse events for your application, and to refresh GUI elements like a progress bar.