【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、网页搜索相关博客

在这片博客上,提供了问题的处理办法,但是没有提供报错原因。

(18条消息) 解决QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.错误_hp_cpp的博客-CSDN博客


3、出现疑问,查看官方文档与源代码

疑问:

  1. isRunning()返回为trueisFinished()返回为false的原因。
  2. 调用abort()close()函数报错QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once的原因。

(1)isRunning()返回为trueisFinished()返回为false的原因。

通过F1,查看官方errorOccurred说明文档,看到当errorOccurred发生时,还没有发生finished。有个处理先后问题。所以解释了isFinished为何false。

image-20230202134233735

查看finished的相关处理逻辑,可以看到有个结束标识,需要修改。

源文件qnetworkreply.cpp中,isFinished()setFinished()的处理逻辑:

image-20230202134438943

image-20230202134458936

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

image-20230202134647386

可以看到,先调用setFinished(true)设置之后,才发射的信号,供外界接收使用。这个时候外界调用isFinished()才会返回true


(2)调用abort()close()函数报错QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once的原因。

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

image-20230202132407050

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

image-20230202133116417

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

image-20230202132726350

在这里,我们就找到了系统给的报错信息QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.

可以看到有一个判断errorCode表示的逻辑:

  1. 第一次调用,errorCode复制为参数code,此时errorCode值不为QNetworkReply::NoError
  2. 第二次调用,errorCode值为上一次调用的code,不为QNetworkReply::NoError,所以会报错。


问题原因总结与处理

问题原因

isRunning()返回为trueisFinished()返回为false的原因

finished的调用是在errorOccurred处理之后,此时QNetworkReply内部标识变量isFinished还是false,即还没有关闭。所以isFinished()返回false

isRunning()调用的是!isFinished()的结果,所以返回true

image-20230202134825887


调用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()函数返回falseisRunning()返回true


处理办法

有两种处理办法:

  1. 一种是在errorOccurred信号槽函数中,不执行abort()或close()逻辑,直接调用deleteLater()关闭。
  2. 另外一种,就是在finished信号槽函数中释放。

推荐第二种,在finished中处理,这样可能会避免其他异常问题。

posted @ 2023-02-02 10:39  声音~  阅读(1654)  评论(0编辑  收藏  举报