如何不错过信号

huangapple go评论65阅读模式
英文:

How not to miss a signal

问题

这段代码使用Qt 5.15编写:

Widget::Widget()
{
    manager = new QNetworkAccessManager(this);
    QNetworkReply *reply = manager->get(QNetworkRequest(QUrl("http://example.com")));
    connect(reply, &QNetworkReply::readyRead, this, &Widget::replyFinished);
}

void Widget::replyFinished(QNetworkReply* reply)
{
    // 在这里进行一些处理
    qDebug() << reply.readAll();
}

请问有其他需要翻译的内容吗?

英文:

I have this code, written in qt 5.15:

Widget::Widget()
{
    manager = new QNetworkAccessManager(this);
    QNetworkReply *reply = manager-&gt;get(QNetworkRequest(QUrl(&quot;http://example.com&quot;)));
    connect(reply, &amp;QNetworkReply::readyRead, this, &amp;Widget::replyFinished));

}
void Widget::replyFinished(QNetworkReply* reply)
{
    // some processing here
    qDebug() &lt;&lt; reply.readAll();
}

It works as expected, but I wonder, is there a risk to miss the signal emmited by QNetworkReply?

I tested that if I had some delay (with QThread::sleep(1);) between the get and the connect, replyFinished is not called.

Is there a method to ask an object to resend a signal if it has been missed?

答案1

得分: 2

I understand your instructions. Here is the translated content:

我不同意接受的答案:如果你移除 QThread::sleep(1) (它只是为了“触发竞态条件”而添加的),那么 if (reply->bytesAvailable() > 0) 将始终返回 false。它对任何情况都提供不了保护。

首先,让我们稍微看一下Qt文档

一个更为复杂的示例,假设管理器已经存在,可以是:

 QNetworkRequest request;
 request.setUrl(QUrl("http://qt-project.org"));
 request.setRawHeader("User-Agent", "MyOwnBrowser 1.0");

 QNetworkReply *reply = manager->get(request);
 connect(reply, &QIODevice::readyRead, this, &MyClass::slotReadyRead);
 connect(reply, &QNetworkReply::errorOccurred,
         this, &MyClass::slotError);
 connect(reply, &QNetworkReply::sslErrors,
         this, &MyClass::slotSslErrors);

就像你所做的,他们首先发送一个 get 请求,然后连接信号。
他们不会在他们的文档中提供一个有错误的示例,这意味着它被认为在生产中是安全的:在连接建立之前数据被接收,不会丢失任何信号。
这也是为什么 if (reply->bytesAvailable() > 0) 将始终返回 false 的原因;网络速度太慢,不可能有其他情况。

如果你仍然想要小心,正确的方法是在发送请求之前将 finished 信号连接到你的槽上**。**
这样,即使你错过了所有的 readyRead 信号,你的槽仍然会被调用至少一次(因为连接在请求发送之前就建立了)。

我稍微修改了你的代码以便它可以编译并显示:

  1. if (reply->bytesAvailable() > 0) 返回 false(文本 Call from if 从未被显示)。
  2. 所有的回复都是通过 readyRead 连接被调用的。
  3. finished 信号在所有数据接收完成后触发最后一次调用 replyFinishedreplyFinished 使用与其余部分相同的 QNetworkReply 指针被调用。
    由于我上面详细说明的原因(没有信号丢失,所以在点 2 中所有的东西都被消耗掉了),在这最后一次调用时那个指针没有任何数据可以读取。

这是修改后的代码:

Widget::Widget()
{
    manager = new QNetworkAccessManager(this);
    QNetworkRequest request;
    request.setUrl(QUrl("http://www.google.com"));
//通过在任何http请求发送之前建立以下连接,确保了安全性。
    connect(manager, &QNetworkAccessManager::finished, this, &Widget::replyFinished);
    QNetworkReply* reply = manager->get(request);
    connect(reply, &QNetworkReply::readyRead, this, 
        [=]() { qDebug() << "Called from readyRead"; this->replyFinished(reply); }
    );
    if (reply->bytesAvailable() > 0) {
        qDebug() << "Call from if";
        replyFinished(reply);
    }
}
void Widget::replyFinished(QNetworkReply* reply)
{
    QString strReply = reply->readAll();
    qDebug() << (void*)reply << " read " << strReply.length() << " characters";
}
英文:

I have to disagree with the accepted answer: if you remove QThread::sleep(1) (that was added only to "trigger a race condition"), then the if (reply-&gt;bytesAvailable() &gt; 0) will always return false. It provides no guard against anything.


First, a small detour to the Qt documentation

A more involved example, assuming the manager is already existent, can be:

 QNetworkRequest request;
 request.setUrl(QUrl(&quot;http://qt-project.org&quot;));
 request.setRawHeader(&quot;User-Agent&quot;, &quot;MyOwnBrowser 1.0&quot;);

 QNetworkReply *reply = manager-&gt;get(request);
 connect(reply, &amp;QIODevice::readyRead, this, &amp;MyClass::slotReadyRead);
 connect(reply, &amp;QNetworkReply::errorOccurred,
         this, &amp;MyClass::slotError);
 connect(reply, &amp;QNetworkReply::sslErrors,
         this, &amp;MyClass::slotSslErrors);

Like you, they first send a get request, then connect signals.<br/>They would not provide a buggy sample in their doc, meaning it was deemed safe for production: no chance signals will be lost because data is received before the connection is established.<br/>That is the very reason why if (reply-&gt;bytesAvailable() &gt; 0) will always return false BTW; a network is just thousands of times too slow for it to be otherwise.


If you still want to be careful, the correct approach is to connect the finished signal to your slot before sending the request.
That way, even if you miss all the readyRead signals, your slot will still be called at least once (since the connection was made before).

I have tweaked your code a bit to make it compile and show:

  1. The if (reply-&gt;bytesAvailable() &gt; 0) returns false (text Call from if is never displayed).
  2. All the reply is called from the readyRead connection.
  3. The finished signal triggers the last call to replyFinished after everything is received; replyFinished is called using the same pointer to QNetworkReply as the rest.<br/>That pointer has, for the reasons I detailed above (no signal is lost so everything gets consummed in point 2), nothing to read on that final call.

Here is the tweaked code:

Widget::Widget()
{
    manager = new QNetworkAccessManager(this);
    QNetworkRequest request;
    request.setUrl(QUrl(&quot;http://www.google.com&quot;));
//Safety is assured by making the following connection
//before any http request is sent.
    connect(manager, &amp;QNetworkAccessManager::finished, this, &amp;Widget::replyFinished);
    QNetworkReply* reply = manager-&gt;get(request);
    connect(reply, &amp;QNetworkReply::readyRead, this, 
        [=]() { qDebug() &lt;&lt; &quot;Called from readyRead&quot;; this-&gt;replyFinished(reply); }
    );
    if (reply-&gt;bytesAvailable() &gt; 0) {
        qDebug() &lt;&lt; &quot;Call from if&quot;;
        replyFinished(reply);
    }
}
void Widget::replyFinished(QNetworkReply* reply)
{
    QString strReply = reply-&gt;readAll();
    qDebug() &lt;&lt; (void*)reply &lt;&lt; &quot; read &quot; &lt;&lt; strReply.length() &lt;&lt; &quot; characters&quot;;
}

答案2

得分: 1

QNetworkReply发送来自另一个线程的信号,因此确实存在潜在的竞争条件。

要防范这种情况,您可以这样做:

Widget::Widget()
{
    manager = new QNetworkAccessManager(this);
    QNetworkReply *reply = manager->get(QNetworkRequest(QUrl("http://example.com")));
    QThread::sleep(1); // 触发竞争条件
    connect(reply, &QNetworkReply::readyRead, this, &Widget::replyFinished);
    if (reply->bytesAvailable() > 0) replyFinished(reply);
}

请注意,replyFinished 可能会被调用两次。

此外,readyRead 并不意味着整个回复已经到达,因此并不意味着 "回复完成"。

英文:

QNetworkReply sends the signal from another thread, so there is indeed a possible race condition here.

To guard against it, you can do this:

Widget::Widget()
{
    manager = new QNetworkAccessManager(this);
    QNetworkReply *reply = manager-&gt;get(QNetworkRequest(QUrl(&quot;http://example.com&quot;)));
    QThread::sleep(1); // trigger race condition
    connect(reply, &amp;QNetworkReply::readyRead, this, &amp;Widget::replyFinished));
    if (reply-&gt;bytesAvailable() &gt; 0) replyFinished(reply);

}

Note that your replyFinished can get called twice.

Also,readyRead does not mean entire reply has arrived, so it does not mean "reply finished".

答案3

得分: 0

My understanding is that the call to QNetworkAccessManager::get merely 'stages' the request: nothing of importance occurs until control is passed back to the Qt event loop.

考虑以下代码片段...

QNetworkAccessManager manager;
QNetworkReply *reply = manager.get(QNetworkRequest(QUrl("https://www.bbc.co.uk")));
dummy_delay();
QObject::connect(reply, &QNetworkReply::finished, qApp,
                 [&]()
                   {
                     std::log << "finished: error = " << reply->error() << '\n';
                     qApp->quit();
                   });

具有以下实现的 dummy_delay...

void dummy_delay ()
{

    /*
     * 阻塞(即不处理事件)休眠 10 秒。
     */
    QThread::sleep(10);
}

在发出 finished 信号时,lambda 会被正确调用:即使有延迟,也不会丢失任何信号(不论延迟持续多长时间,我测试过最多 1 分钟)。然而,使用下一个实现...

void dummy_delay ()
{

    /*
     * 处理事件 10 秒。
     */
    QEventLoop el;
    QTimer::singleShot(10000, [&]{ el.quit(); });
    el.exec();
}

控制会返回到事件循环,假设下载时间少于 10 秒,则 finished 信号会被“丢失”,lambda 永远不会被调用。

因此,在 get 请求之后调用 connect 是安全的,前提是中间的代码在任何时候都不会将控制返回到事件循环。

[请注意,上述内容仅基于经验,因为我尚未有机会研究相关的 Qt 源代码。]

英文:

My understanding is that the call to QNetworkAccessManager::get merely 'stages' the request: nothing of importance occurs until control is passed back to the Qt event loop.

Consider the code snippet...

QNetworkAccessManager manager;
QNetworkReply *reply = manager.get(QNetworkRequest(QUrl(&quot;https://www.bbc.co.uk&quot;)));
dummy_delay();
QObject::connect(reply, &amp;QNetworkReply::finished, qApp,
                 [&amp;]()
                   {
                     std::log &lt;&lt; &quot;finished: error = &quot; &lt;&lt; reply-&gt;error() &lt;&lt; &#39;\n&#39;;
                     qApp-&gt;quit();
                   });

With the following implementation of dummy_delay...

void dummy_delay ()
{

    /*
     * Blocking (i.e. no events processed) sleep for 10 seconds.
     */
    QThread::sleep(10);
}

the lambda is called correctly when the finished signal is emitted: i.e. no signal is lost despite the delay (and regardless of the delay duration -- I tested up to 1 minute). With the next implementation however...

void dummy_delay ()
{

    /*
     * Process events for 10 seconds.
     */
    QEventLoop el;
    QTimer::singleShot(10000, [&amp;]{ el.quit(); });
    el.exec();
}

control returns to the event loop and -- assuming the download takes less than 10s -- the finished signal is 'lost' and the lambda never called.

So it's safe to call connect after the get request provided the intervening code does not, at any point, return control to the event loop.

[Note that the above is purely empirical as I haven't had a chance to study the relevant Qt source code yet.]

huangapple
  • 本文由 发表于 2023年4月6日 23:34:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/75951289.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定