【Qt】排查QNetworkReply在信号errorOccurred处理槽中abort或close报错原因
问题复现
项目开发中自定义了实现一个http文件下载组件,在处理errorOccurred信号时,原意是在此信号槽中,关闭QNetworkReply连接,同时释放对应资源。代码示例如下:
void FileDownloadProgress::slot_network_reply_errorOccurred(QNetworkReply::NetworkError error)
{
qDebug() << __FUNCTION__ << ",error:" << error << "\n";
if (m_network_reply) {
if (m_network_reply->isRunning()) { // 这里会返回true。
m_network_reply->abort(); // 执行这句话时,就会报错,注释掉就没问题。
}
disconnect(m_network_reply);
m_network_reply->deleteLater();
m_network_reply = nullptr;
}
}
在实际运行时,isRunning()返回为true,执行abort(),会报错如下错误:
QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.
问题排查
1、测试其他组合
刚开始以为是函数调用错误,也查看文档,并实验其他组合如下,也还是会报错。
if (m_network_reply->isRunning()) { // 报错组合
m_network_reply->close();
}
if (m_network_reply->isFinished()) { // 报错组合
m_network_reply->close();
}
2、网页搜索相关博客
在这片博客上,提供了问题的处理办法,但是没有提供报错原因。
3、出现疑问,查看官方文档与源代码
疑问:
isRunning()返回为true或isFinished()返回为false的原因。- 调用
abort()或close()函数报错QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once的原因。
(1)isRunning()返回为true 或 isFinished()返回为false的原因。
通过F1,查看官方errorOccurred说明文档,看到当errorOccurred发生时,还没有发生finished。有个处理先后问题。所以解释了isFinished为何false。

查看finished的相关处理逻辑,可以看到有个结束标识,需要修改。
源文件qnetworkreply.cpp中,isFinished()和setFinished()的处理逻辑:


源文件qnetworkreplyhttpimpl.cpp中,相关处理逻辑:

可以看到,先调用setFinished(true)设置之后,才发射的信号,供外界接收使用。这个时候外界调用isFinished()才会返回true。
(2)调用abort()或close()函数报错QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once的原因。
abort()的函数源代码如下(在文件qnetworkreplyhttpimpl.cpp中),可以看到调用了error(xxx,xxx)函数:

close()的函数源代码如下(在文件qnetworkreplyhttpimpl.cpp中),可以看到同样调用了error(xxx,xxx)函数:

而error(xxx,xxx)函数实现具体如下(在文件qnetworkreplyhttpimpl.cpp中):

在这里,我们就找到了系统给的报错信息QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.。
可以看到有一个判断errorCode表示的逻辑:
- 第一次调用,
errorCode复制为参数code,此时errorCode值不为QNetworkReply::NoError。 - 第二次调用,
errorCode值为上一次调用的code,不为QNetworkReply::NoError,所以会报错。
问题原因总结与处理
问题原因
isRunning()返回为true 或 isFinished()返回为false的原因
finished的调用是在errorOccurred处理之后,此时QNetworkReply内部标识变量isFinished还是false,即还没有关闭。所以isFinished()返回false。
而isRunning()调用的是!isFinished()的结果,所以返回true。

调用abort()或close()函数报错QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once的原因
abort()和close()的内部实现,会调用error(),而error()会调用errorOccurred()的信号处理函数。
但是errorOccurred()内部有变量标识,避免重复执行,当检测到重复执行,就会报错QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once。
在errorOccurred的明白当错误发生时,QNetworkReply的连接实际上已经关闭了,但是可能还没有回调到finished信号槽,导致isFinished()函数返回false,isRunning()返回true。
处理办法
有两种处理办法:
- 一种是在
errorOccurred信号槽函数中,不执行abort()或close()逻辑,直接调用deleteLater()关闭。 - 另外一种,就是在
finished信号槽函数中释放。
推荐第二种,在finished中处理,这样可能会避免其他异常问题。

浙公网安备 33010602011771号