Количество потоков увеличивается, даже при удалении потоков
Имейте приложение, в котором у меня есть 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). Когда больше нет загружаемых файлов, файл-загрузчик сигнализирует "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, которые я удаляю, и все, кажется, удаляется, но все же я царапаю себе голову, когда количество строк продолжает увеличиваться...
EDIT:
В моем файловом загрузчике я создаю объект (диспетчер) в куче, в котором есть стек QNetworkAccessManager. Когда файловый загрузчик удаляется, он вызывает "deleteLater()" в Менеджере, но он никогда не удаляется. Мы попытались удалить Диспетчер и установить его в NULL, но это дало нам нарушение доступа, так как Менеджер еще не был выполнен (QNetwork.dll сообщила об ошибке, так что это должно быть что-то внутри QNAM, который все еще работает). Время, когда мы не получили нарушение доступа, объект был удален, и количество потоков вернулось к нормальному состоянию. Что может жить внутри QNAM и мешать мне удалять его, когда он выходит за рамки? Должен ли я создать QNAM вместо кучи? На этом этапе не деструкторы вызываются даже при вызове функции deleteLater()...
Также, как уменьшить количество меток?
Ответы
Ответ 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 секунд или около того (несколько "потоков наблюдателей" должны быть убиты окнами и т.д.).
Ответ 2
Возможно, я ошибаюсь, но я думаю, что есть проблемы с вашими сигналами:
// 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);
Помните, что когда несколько слотов подключены к одному и тому же сигналу, они выполняются в порядке соединения. Здесь, когда файл-загрузчик будет завершен, он вызовет finished()
, который сначала вызовет метод quit()
потока, а затем метод deleteFinishedUploader()
. То же самое для сигнала canceled()
.
Но, тем временем, поток был закончен, поэтому обработка событий для fileUploader не может быть выполнена (следствие moveToThread(...)
). deleteLater()
нужна обработка событий, поэтому ваш файловый загрузчик никогда не будет удален...
Я не на 100%, что упорядочение ваших подключений по-другому приведет к работе: можно вызвать deleteLater()
и поток сразу после него, без обработки событий.
Решение может состоять в том, чтобы вернуть файл-загрузчик в основной поток или в поток, который все еще обрабатывает цикл событий.
Ответ 3
Не ответ, но:
fileUploaderThread->start();
QMetaObject::invokeMethod(fileUploader, "init", Qt::QueuedConnection);
QMetaObject::invokeMethod(fileUploader, "uploadAt", Qt::QueuedConnection, Q_ARG(int, startIndex));
означает, что вы начинаете четный цикл, а затем вы выстраиваете в очередь слоты или сигналы, которые нужно выполнить. Предположим (в общем), что в этом потоке есть другие QObject
. Возможно, они получат свои слоты или сигналы, выполненные, потому что цикл событий уже запущен. Если вы хотите, чтобы "init" и "uploadAt" были первыми методами, вызываемыми при запуске цикла событий, вы ставите их в очередь перед запуском цикла событий (если поток не запущен, они никогда не будут исполняться).
От QMetaObject:: invokeMethod:
Если тип Qt:: QueuedConnection, QEvent будет отправлен, и член будет вызван, как только приложение войдет в основной цикл событий.
В этом случае событие отправляется в цикл событий потока.