13

有一个应用程序,其中我有 QOBJects,它们都包含一个 QNetworkAccessManager。我知道建议只对每个应用程序进行一次调用,但由于我同时进行了 6 次以上的调用,因此我需要这样。所以,这就是我启动线程的方式。

FileUploader *fileUploader = new FileUploader(_fileList);
QThread *fileUploaderThread = new QThread();
fileUploader->moveToThread(fileUploaderThread);

// uploader > model
connect(fileUploader, SIGNAL(progressChangedAt(int)), _model, SLOT(reportProgressChanged(int)), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(statusChangedAt(int)), _model, SLOT(reportStatusChanged(int)), Qt::QueuedConnection);
// uploader > its thread
connect(fileUploader, SIGNAL(canceled()), fileUploaderThread, SLOT(quit()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), fileUploaderThread, SLOT(quit()), Qt::QueuedConnection);
// uploader > this
connect(fileUploader, SIGNAL(canceled()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finishedCurrentUpload()), this, SLOT(uploadNextFileOrFinish()), Qt::QueuedConnection);
// thread > this
connect(fileUploaderThread, SIGNAL(finished()), this, SLOT(checkIfAllThreadsAreFinished()), Qt::QueuedConnection);
connect(fileUploaderThread, SIGNAL(finished()), this, SLOT(deleteFinishedThread()), Qt::QueuedConnection);
// this > uploader
connect(this, SIGNAL(cancel()), fileUploader, SLOT(cancel()), Qt::QueuedConnection);

fileUploaderThread->start();
QMetaObject::invokeMethod(fileUploader, "init", Qt::QueuedConnection);
QMetaObject::invokeMethod(fileUploader, "uploadAt", Qt::QueuedConnection, Q_ARG(int, startIndex));

QMutexLocker locker(&_mutex);
_threadCount++;

每个线程都以列表的索引开始,以便他们可以获取他们需要上传的内容并继续执行大约 5 个步骤(使用 QNetworkAccessManager 调用)。当没有更多项目要上传时,fileUploader 会发出“finished()”信号,它会调用我所做的deleteFinishedThread事情deleteFinishedUploader

QThread *thread = qobject_cast<QThread*>(sender());

if(thread != NULL) thread->deleteLater();

或者

FileUploader *fileUploader = qobject_cast<FileUploader*>(sender());

if(fileUploader != NULL) fileUploader->deleteLater();

这些假设在完成后删除线程。

问题是,每次我启动(例如)3 个线程,每个线程都有 1 个文件要上传和处理,线程数会增加 8-10。这意味着如果我重新启动上传过程几次,线程数将从大约 5 变为 100。

我究竟做错了什么?还是我最大的问题是我使用“Windows 任务管理器”来控制它?我正在处理我删除的 QNAM 的所有回复,所有内容似乎都被删除了,但是当线程数不断增加时,我仍然摸不着头脑......

编辑:在我的文件上传器中,我在堆栈上创建了一个对象(管理器),它在堆栈上有一个 QNetworkAccessManager。当文件上传器被删除时,它会在管理器上调用“deleteLater()”,但它永远不会被删除。我们试图删除 Manager 并将其设置为 NULL,但这给了我们访问冲突,因为 Manager 还没有完成(QNetwork.dll 报告了这个问题,所以它必须是 QNAM 内部仍在运行的东西)。当我们没有遇到访问冲突时,对象被删除并且线程数恢复正常。QNAM 中有什么可以阻止我在超出范围时删除它?我应该在堆上创建 QNAM 吗?在这个阶段,即使调用 deleteLater(),也不会调用任何析构函数......

另外,如何减少句柄数?

4

3 回答 3

7

我可能是错的,但我认为你的信号有问题:

// uploader > its thread
connect(fileUploader, SIGNAL(canceled()), fileUploaderThread, SLOT(quit()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), fileUploaderThread, SLOT(quit()), Qt::QueuedConnection);
// uploader > this
connect(fileUploader, SIGNAL(canceled()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);

请记住,当多个插槽连接到同一个信号时,它们会按照连接顺序执行。在这里,当fileUploader完成时,它finished()会先调用quit()线程的方法,然后调用deleteFinishedUploader()方法。信号也一样canceled()。但是,与此同时,线程已完成,因此无法对 fileUploader 进行任何事件处理(的后果moveToThread(...))。deleteLater()需要事件处理,因此您的 fileUploader 将永远不会被删除...

我不是 100% 以另一种方式安排你的连接会让事情正常工作:deleteLater()可以调用并且线程在之后立即退出,没有事件处理。

解决方案可能是moveToThread()将 fileUploader 重新分配给主线程,或者重新分配给仍在处理其事件循环的线程。

于 2012-04-24T08:52:07.490 回答
2

不是答案,而是:

    fileUploaderThread->start();
    QMetaObject::invokeMethod(fileUploader, "init", Qt::QueuedConnection);
    QMetaObject::invokeMethod(fileUploader, "uploadAt", Qt::QueuedConnection, Q_ARG(int, startIndex));

意味着您启动偶数循环,然后将要执行的插槽或信号排队。假设(通常)QObject该线程中还有其他。由于事件循环已经开始,它们可能会执行它们的插槽或信号。如果您希望“init”和“uploadAt”成为事件循环运行时首先调用的方法,则在启动事件循环之前将它们排队(如果线程未启动,它们将永远不会被执行)。

来自QMetaObject::invokeMethod

如果 type 是 Qt::QueuedConnection,则将发送一个 QEvent 并在应用程序进入主事件循环后立即调用该成员。

在这种情况下,事件被发送到线程事件循环。

于 2012-04-27T08:14:51.933 回答
1

经过大量“几乎放弃”后,我想出了线程的解决方案。Synxis 关于插槽顺序的说法是真实的。

但是,我仍然对文件句柄有一些问题,如果有人有更好的答案,我很乐意接受。

我将代码更改为:

...
// uploader > this
connect(fileUploader, SIGNAL(canceled()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
// uploader > its thread
connect(fileUploader, SIGNAL(destroyed()), fileUploaderThread, SLOT(quit()));

这意味着当对象被删除时线程会停止(quit())。即使文档指出,这实际上也有效:

该信号在对象 obj 被销毁之前立即发出,并且不能被阻塞。

发出此信号后,立即销毁所有对象的子对象。

这意味着这个信号会在任何东西被破坏之前发出(这意味着我会在其中的上传者被删除之前退出线程)?还不够好,这可能是一个更好的方法。但是,atm,每次上传者完成并在 20 秒左右后恢复正常时,我的线程数都会下降很多(一些“观察者线程”必须被 Windows 等杀死)。

于 2012-04-24T09:39:56.147 回答