【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
中处理,这样可能会避免其他异常问题。