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();

これらは、完了時にスレッドを削除することを想定しています。

問題は、(たとえば) 1 つのファイルをアップロードして処理する 3 つのスレッドを開始するたびに、スレッド数が 8 ~ 10 増加することです。これは、アップロード プロセスを数回再開すると、スレッド カウントが約 5 から 100 になることを意味します。

私は何を間違っていますか?それとも、「Windows タスク マネージャー」を使用してこれを制御することが最大の問題ですか? 削除した QNAM からのすべての返信を処理しており、すべてが削除されたように見えますが、スレッド数が増え続けると頭を悩ませます...

編集: 私のファイルアップローダーでは、スタックに QNetworkAccessManager を持つヒープにオブジェクト (マネージャー) を作成します。fileuploader が削除されると、Manager で「deleteLater()」が呼び出されますが、削除されることはありません。Manager を削除して NULL に設定しようとしましたが、Manager がまだ完了していないため、アクセス違反が発生しました (QNetwork.dll が問題を報告したため、まだ実行されている QNAM 内の何かである必要があります)。アクセス違反が発生せず、オブジェクトが削除され、スレッド数が正常に戻ったとき。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 に答える