QTimer 槽中的 QEventLoop

QEventLoop in QTimer slot

我在同一个线程中有多个QTimer,在连接QTimer的slot中,我使用QEventLoop进行同步,比如http请求,但是我发现不同的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 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应该不会受到影响

案例二

只要改变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 32 位,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();

在timer slot中,只有一个sync http request,周期性的轮询数据。 正常情况下请求会很快完成,但有时出错会花费1000ms以上。 我的应用程序需要同时与多个服务器通信,每个连接都有一个定时器来进行轮询,所有连接实例都在同一个线程中,所以所有定时器共享同一个事件循环。 我的测试是一种模拟请求的简单方法,使用 QTimer::singleShot 作为请求的时间成本。 有没有更好的方法在 QT 中同步 http 请求?使用 QEventLoop 可能不是最好的。

首先,检查您的 QTimer::timerType():默认值为 Qt::CoarseTimer,精度为 5%。您可能需要 Qt::PreciseTimer

其次,Qt 主事件循环将序列化所有事件。不管他们是什么。这是一个独特的管道(很容易成为您应用程序的瓶颈,顺便说一句)。它也有在处理之前将一些事件“打包”在一起(如鼠标事件)的坏习惯。

最后,QTimer::singleShot 也是一个真正的计时器,您可以在插槽内启动一个 - 基本上,它是在事件循环上下文中执行的,您可以在其中手动调用事件循环处理,在两个 distinct QEventLoop 个对象...

老实说,您的计时器做奇怪的事情并不奇怪。你能准确地说出你需要用这些计时器做什么吗?因为你的 QEventLoop 技巧做的唯一一件事就是在请求的时间段内 block/mess 计时器,即 T1 为 3000 毫秒,T2 为 100 毫秒,这显然会扰乱你请求的基本 1000 毫秒时间段每个...

通常,您将接收信号(QNetworkAccessManagerfinished(QNetworkReply*),但 QIODevicereadyRead())连接到您的插槽,然后使用 sender() 允许单个插槽同时处理各种对象。你不需要手动同步任何东西,你让 Qt 为你处理——这是使用框架的好处之一。于是,“回复接收”就这样完成了。

是否发送请求,由您决定。您可以使用各种解决方案:

  1. 请求后尽快发送(即通过单击按钮)。
  2. 将它们缓冲到 FIFO 容器中,使用 QTimer 发送它们而不会同时破坏您的机器和服务器。请求被尽快推入缓冲区。
  3. 线程这部分 - 注意,使用 Qt,您需要大部分时间调用 moveToThread() 才能使其正常工作。

我更喜欢解决方案 #2,每个目标服务器都有一个队列和一个特定的计时器 - 这将允许非常基本的 QoS,并将避免对一个服务器的大量请求禁止使用其他服务器.在此之前,您可以使用调度程序功能,该功能会通过关联服务器名称(如果需要+协议)及其特定队列和计时器的映射自动选择要使用的正确队列。

当你不做任何事情,既不请求(它们都被发送)并且你在等待回复时,你让主 Qt 事件循环完成它的工作:避免阻塞消息泵 - 它会产生“应用程序没有响应” 消息。此外,它还可以减少 CPU 负载,获取应用程序的所有鼠标事件,并刷新进度条等 GUI 元素。