英文:
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->get(QNetworkRequest(QUrl("http://example.com")));
connect(reply, &QNetworkReply::readyRead, this, &Widget::replyFinished));
}
void Widget::replyFinished(QNetworkReply* reply)
{
// some processing here
qDebug() << 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
信号,你的槽仍然会被调用至少一次(因为连接在请求发送之前就建立了)。
我稍微修改了你的代码以便它可以编译并显示:
if (reply->bytesAvailable() > 0)
返回 false(文本Call from if
从未被显示)。- 所有的回复都是通过
readyRead
连接被调用的。 finished
信号在所有数据接收完成后触发最后一次调用replyFinished
;replyFinished
使用与其余部分相同的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->bytesAvailable() > 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("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);
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->bytesAvailable() > 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:
- The
if (reply->bytesAvailable() > 0)
returns false (textCall from if
is never displayed). - All the reply is called from the
readyRead
connection. - The
finished
signal triggers the last call toreplyFinished
after everything is received;replyFinished
is called using the same pointer toQNetworkReply
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("http://www.google.com"));
//Safety is assured by making the following connection
//before any http request is sent.
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";
}
答案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->get(QNetworkRequest(QUrl("http://example.com")));
QThread::sleep(1); // trigger race condition
connect(reply, &QNetworkReply::readyRead, this, &Widget::replyFinished));
if (reply->bytesAvailable() > 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("https://www.bbc.co.uk")));
dummy_delay();
QObject::connect(reply, &QNetworkReply::finished, qApp,
[&]()
{
std::log << "finished: error = " << reply->error() << '\n';
qApp->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, [&]{ 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.]
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论