使用 QNetworkAccessManager 时启动 QThread 失败

Posted

技术标签:

【中文标题】使用 QNetworkAccessManager 时启动 QThread 失败【英文标题】:Failed to start QThread when using QNetworkAccessManager 【发布时间】:2017-03-05 20:58:35 【问题描述】:

我目前正在尝试制作一个可以从 Google 云端硬盘下载大量文件的软件。目前下载不成问题。

不过,我在启动 500 多个同时下载时遇到了问题。我使用的是本教程的略微修改版本:https://wiki.qt.io/Download_Data_from_URL。

这是 .h 文件:

class FileDownloader : public QObject

    Q_OBJECT
public:
    explicit FileDownloader(QUrl url, QObject *parent = 0, int number = 0);
    QByteArray downloadedData() const;
    void launchNewDownload(QUrl url);
    QByteArray m_DownloadedData;
    QNetworkReply* reply;

    static QNetworkAccessManager *m_WebCtrl;

signals:
    void downloaded();

private slots:
    void fileDownloaded(QNetworkReply* pReply);
;

这是 .cpp 文件:

QNetworkAccessManager* FileDownloader::m_WebCtrl = nullptr;

FileDownloader::FileDownloader(QUrl url, QObject *parent) :
    QObject(parent)

    if (m_WebCtrl == nullptr) 
        m_WebCtrl = new QNetworkAccessManager(this);
    
    connect(m_WebCtrl, SIGNAL (finished(QNetworkReply*)),this, SLOT (fileDownloaded(QNetworkReply*)));

    launchNewDownload(url);


void FileDownloader::launchNewDownload(QUrl url) 
    QNetworkRequest request(url);
    this->reply = m_WebCtrl->get(request);


void FileDownloader::fileDownloaded(QNetworkReply* pReply) 
    m_DownloadedData = pReply->readAll();

    //emit a signal
    pReply->deleteLater();
    emit downloaded();


QByteArray FileDownloader::downloadedData() const 
    return m_DownloadedData;

当下载到大约第 500 次时,问题是“QThread::start: Failed to create thread ()”。我试图限制同时运行的下载数量 - 但我总是遇到同样的问题。此外,我尝试在完成任务时删除每个下载器 - 它除了使程序崩溃之外什么也没做;)

我认为它来自唯一进程允许的线程数,但我无法解决它!

有人有什么想法可以帮助我吗?

谢谢!

【问题讨论】:

您是否正在启动自己的线程来运行请求?或者您是否有多个QNetworkAccessManager 实例(可能每个请求一个)?您的目标不需要以上两者。 您只需要QNetworkAccessManager 的一个实例和您的主线程(仅此而已)。使用 QNetworkAccessManager 提供的异步 API。尽可能让 Qt 处理并行化请求的低级细节,你应该没问题。 我有多个 QNetworkAccessManager 实例,但只有主线程。当我尝试仅使用 QNetworkAccessManager 的一个(静态)实例时,我的程序出现了奇怪的行为。它不再起作用了,文件立即下载,没有任何内容......而且文件比预期的要多! 您需要在您的问题中添加一个MCVE 才能回答。您必须在代码中做错了才会发生这种情况。 对不起 MCVE,我不使用 *** !这是文件,希望够清楚。 " 当我将信号连接到插槽时,它会断开前一个插槽。".不,这不是真的。当连接新的信号/槽时,Qt 从不断开旧的槽。您甚至可以多次连接相同的信号/插槽对(这将导致每当发出信号时都会多次调用插槽)。 【参考方案1】:

QNetworkAccessManager::finished 信号被记录为在待处理的网络回复完成时发射

这意味着如果QNetworkAccessManager 用于一次运行多个请求(这是perfectly normal usage)。 finished 信号将为每个请求发出一次。由于您在 FileDownloader 对象之间有一个共享的 QNetworkAccessManager 实例,因此每次调用 get 都会发出 finished 信号。因此,一旦第一个 FileDownloader 完成下载,所有 FileDownloader 对象都会收到 finished 信号。

您可以使用QNetworkReply::finished,而不是使用QNetworkAccessManager::finished,以避免混淆信号。这是一个示例实现:

#include <QtNetwork>
#include <QtWidgets>

class FileDownloader : public QObject

    Q_OBJECT
    //using constructor injection instead of a static QNetworkAccessManager pointer
    //This allows to share the same QNetworkAccessManager
    //object with other classes utilizing network access
    QNetworkAccessManager* m_nam;
    QNetworkReply* m_reply;
    QByteArray m_downloadedData;

public:
    explicit FileDownloader(QUrl imageUrl, QNetworkAccessManager* nam,
                            QObject* parent= nullptr)
        :QObject(parent), m_nam(nam)
    
        QNetworkRequest request(imageUrl);
        m_reply = m_nam->get(request);
        connect(m_reply, &QNetworkReply::finished, this, &FileDownloader::fileDownloaded);
    
    ~FileDownloader() = default;

    QByteArray downloadedData()constreturn m_downloadedData;

signals:
    void downloaded();
private slots:
    void fileDownloaded()
        m_downloadedData= m_reply->readAll();
        m_reply->deleteLater();
        emit downloaded();
    
;

//sample usage
int main(int argc, char* argv[])
    QApplication a(argc, argv);

    QNetworkAccessManager nam;
    FileDownloader fileDownloader(QUrl("http://i.imgur.com/Rt8fqpt.png"), &nam);
    QLabel label;
    label.setAlignment(Qt::AlignCenter);
    label.setText("Downloading. . .");
    label.setMinimumSize(640, 480);
    label.show();
    QObject::connect(&fileDownloader, &FileDownloader::downloaded, [&]
        QPixmap pixmap;
        pixmap.loadFromData(fileDownloader.downloadedData());
        label.setPixmap(pixmap);
    );

    return a.exec();


#include "main.moc"

如果您使用此方法下载大文件,请考虑查看this question。

【讨论】:

谢谢,成功了!我不知道 QNetworkReply 有一个信号“完成”...【参考方案2】:

一种解决方案是使用QThreadPool。您只需为其提供任务 (QRunnable),它就会为您处理线程数和任务队列。

但是,在您的情况下,这并不完美,因为您将同时下载的数量限制为 QThreadPool 创建的线程数(通常是您拥有的 CPU 内核数)。

要克服这个问题,您必须自己处理 QThread,而不是使用 QThreadPool。您将创建少量线程(请参阅QThread::idealThreadCount())并在每个 QThread 上运行多个FileDownloader

【讨论】:

请注意,在使用QNetworkAccessManager 时,通常不需要使用QThreadPool来自文档:“QNetworkAccessManager 将它收到的请求排队。并行执行的请求数量取决于协议。目前,对于桌面平台的 HTTP 协议,一个主机/端口组合并行执行 6 个请求。". QNetworkAccessManager 已经处理了这些底层细节,它在内部设计为适合 QtWebKit 的工作方式,请参阅 here。

以上是关于使用 QNetworkAccessManager 时启动 QThread 失败的主要内容,如果未能解决你的问题,请参考以下文章

离线使用 QNetworkAccessManager

跨 dll 使用 QNetworkAccessManager

使用 QNetWorkAccessManager 将值传递给插槽

如何使用 Qt/QNetworkAccessManager (C++) 实现 SFTP

如何使用 QNetworkAccessManager 找出数据传输延迟

使用 QNetworkAccessManager 时如何处理代理