使用 QNetworkAccessManager 时启动 QThread 失败

Failed to start QThread when using QNetworkAccessManager

我目前正在尝试制作一个可以从 Google Drive 下载大量文件的软件。下载目前没有问题。

然而,我在同时启动 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;
}

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

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

有没有人有想法可以帮助我?

谢谢!

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()const{return 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"

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

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

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

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