使用 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 失败的主要内容,如果未能解决你的问题,请参考以下文章
跨 dll 使用 QNetworkAccessManager
使用 QNetWorkAccessManager 将值传递给插槽
如何使用 Qt/QNetworkAccessManager (C++) 实现 SFTP