我有这个代码,写在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();
}
按预期工作,但我想知道,是否有错过QNetworkReply
发出的信号的风险?
我测试了如果在get
和connect
之间有一些延迟(使用QThread::sleep(1);
),则不调用replyFinished
。
是否有方法要求对象在错过信号时重新发送信号?
我不得不不同意接受的答案:如果您删除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
请求,然后连接信号。
他们不会在文档中提供有bug的样本,这意味着它被认为对生产是安全的:没有机会丢失信号,因为在连接建立之前接收到数据。
这就是为什么if (reply->bytesAvailable() > 0)
总是返回false的原因;网络的速度是它的千分之一。
如果您仍然想要小心,正确的方法是在之前将finished
信号连接到您的插槽发送请求。这样,即使您错过了所有readyRead
信号,您的插槽仍将至少被调用一次(因为之前已经建立了连接)。
我稍微调整了一下你的代码,使它可以编译并显示:
if (reply->bytesAvailable() > 0)
返回false(文本Call from if
不显示)。- 所有的应答都是从
readyRead
连接调用的。 finished
信号触发对replyFinished
的最后一次调用;使用指向QNetworkReply
的相同指针调用replyFinished
。
由于我上面详细说明的原因(没有信号丢失,所以在第2点中所有内容都被消耗),该指针在最后调用中没有任何可读的内容。
调整后的代码如下:
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";
}
QNetworkReply从另一个线程发送信号,因此这里确实可能存在竞争条件。
要防止它,可以这样做:
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);
}
注意你的replyFinished可以被调用两次。
同样,readyRead
并不表示整个回复已经到达,所以并不表示回复已完成。
我的理解是对QNetworkAccessManager::get
的调用仅仅是"阶段"的请求:没有任何重要的事情发生,直到控制被传递回Qt事件循环。
考虑以下代码片段…
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 ()
{
/*
* Blocking (i.e. no events processed) sleep for 10 seconds.
*/
QThread::sleep(10);
}
当finished
信号发出时,lambda被正确调用:即,尽管有延迟,但没有信号丢失(无论延迟持续时间如何—我测试了长达1分钟)。然而,对于下一个实现…
void dummy_delay ()
{
/*
* Process events for 10 seconds.
*/
QEventLoop el;
QTimer::singleShot(10000, [&]{ el.quit(); });
el.exec();
}
控件返回到事件循环,并且——假设下载时间少于10秒——finished
信号"丢失"并且lambda从未被调用。
因此,在get
请求之后调用connect
是安全的,只要中间代码在任何时候都不将控制返回给事件循环。
[注意,以上是纯粹的经验,因为我还没有机会研究相关的Qt源代码]