线程数量增加很多,即使删除这些线程。

13

我的应用程序中有许多包含QNetworkAccessManager的QOBJECTS。我知道建议只在应用程序中使用一个,但由于我同时进行了超过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()"信号,该信号调用deleteFinishedThreaddeleteFinishedUploader,在这里我执行以下操作:

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。当文件上传程序被删除时,它在Manager上调用“deleteLater()”,但它从未被删除。我们试图删除Manager并将其设置为NULL,但由于Manager尚未完成(QNetwork.dll报告了问题,因此必须是QNetworkAccessManager内部仍在运行的某些内容),所以导致了访问冲突。在我们没有遇到访问违规的情况下,对象被删除且线程计数恢复正常。在QNetworkAccessManager中可能存在什么可以阻止我在超出范围时将其删除的内容?我应该在堆上创建QNAM吗?在这个阶段,即使调用deleteLater(),也没有任何析构函数被调用......

另外,如何减少句柄计数?


1
增加了悬赏。你的问题需要一些关注... - UmNyobe
一个注释:如果您仍在使用它们,请在启动fileUploaderThread之前调用QMetaObject::invokeMethod(。这是确保这些槽在线程循环开始时首先被调用的最佳方法。 - UmNyobe
没有QMetaObject :: invokeMethod将在事件循环中堆栈起来“init”,并且将首先执行。这就是在QApplication a(argc,argv); MainWindow w; w.show(); return a.exec();中发生的情况。show()在应用程序甚至循环开始时立即调用。(请参见invoke method文档) - UmNyobe
我还不太确定你的意思。我的方法按照我想要的方式工作,但如果有任何问题,如果您能发送给我一个链接或更长的评论来解释为什么这不起作用,我将不胜感激。目前,构造函数在对象创建时被调用。当对象在线程中时,所有堆分配等都会在对象在线程中时创建(这意味着当我使用“this”作为父级创建它们时,“this”将在线程中而不是主线程中)。 - chikuba
如果您能解释一下“这是确保线程循环开始时首先调用这些插槽的最佳方法”,我仍然会非常感激,因为我可能完全不明白您在说什么 :) - chikuba
显示剩余2条评论
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永远不会被删除...。
我不能百分之百确定以另一种方式安排连接就能使事情正常工作:可以立即调用deleteLater()并退出线程,而没有进行事件处理。
解决方案可能是将fileUploader重新moveToThread()到主线程或仍在处理其事件循环的线程。

2
这不是一个答案,但是:
    fileUploaderThread->start();
    QMetaObject::invokeMethod(fileUploader, "init", Qt::QueuedConnection);
    QMetaObject::invokeMethod(fileUploader, "uploadAt", Qt::QueuedConnection, Q_ARG(int, startIndex));

这段文字的意思是:你启动了事件循环,然后将要执行的槽或信号排队等待执行。假设(一般情况下)在此线程中还有其他 QObject。这些 QObject 可能会因为事件循环已经启动而先于你的“init”和“uploadAt”方法执行它们自己的槽或信号。如果你想让“init”和“uploadAt”方法在事件循环运行时首先被调用,你需要在启动事件循环之前将它们排队(如果线程没有启动,它们永远不会被执行)。
从 QMetaObject::invokeMethod 中可以看到:
如果类型是 Qt::QueuedConnection,则会发送一个 QEvent,并在应用程序进入主事件循环时立即调用该成员函数。
在这种情况下,事件被发送到线程事件循环。

哦,好的。知道真不错,谢谢!然而,在这个例子中,我可能会坚持我已经拥有的东西,因为上传者将是那个主题中唯一的元素 :) - chikuba

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 被销毁之前立即发射,且无法阻止。

此信号发射后,该对象的所有子对象均会立即被销毁。

这意味着这个信号会在任何东西被销毁之前发出(这意味着在 uploader 被删除之前我将退出线程)? 这并不是很好,可能存在更好的方法。然而,目前我的线程计数在每次上传器完成后都会减少相当多,并在 20 秒左右恢复正常(一些 "watcher-threads" 必须被 Windows 等终止)。


插槽的解决方案不错。文件句柄是如何管理/创建/删除的?它们存在什么问题? - Synxis
我不太确定。我有一些C#的经验,但现在我只能看到句柄数量在增加而不是减少。 - chikuba
问题在于我不确定在对象被销毁之前删除线程是否安全。 - chikuba
如果你使用 'deleteLater()' 来删除对象,那么不行,因为它需要一个事件循环才能工作。你必须在删除线程之前删除对象或将对象移动到另一个线程。 - Synxis
不太确定您的意思。目前它可以正常工作,并且上传程序中的所有内容都会被删除,包括所在的线程。这可能是因为上传程序的基类QObject是最后一个被调用的,当它发出“destroyed”信号(将销毁线程)时,实际上没有其他可以被删除的东西了。是的,在~QObject中仍然有一些非常重要的事情要做,因此我将把对象移动到主线程中。 - chikuba

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接