QThreads
の使用に問題があり、適切な組み合わせを見つける前にさまざまな組み合わせを検討する必要がありました。ただし、イベントループとシグナルスロット処理に関して、以下に示す4つのケースで実際に何が起こっているのかまだ完全には理解していません。
OUTPUTセクションにコメントをいくつか追加しましたが、ご覧のとおり、観察された動作の原因についての私の仮定が正しいかどうかはわかりません。また、_case 3
_が実際のコードで使用される可能性があるものかどうかもわかりません。これが私のテストコードです(ケースごとに_main.cpp
_のみが異なります):
worker.h:
_#include <QObject>
#include <QDebug>
#include <QThread>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = 0) { this->isRunning_ = false;}
bool isRunning() const { return isRunning_; }
signals:
void processingFinished();
void inProgress();
public slots:
void process()
{
this->isRunning_ = true;
qDebug() << this << "processing started";
for (int i = 0; i < 5; i++)
{
QThread::usleep(1000);
emit this->inProgress();
}
qDebug() << this << "processing finished";
this->isRunning_ = false;
emit this->processingFinished();
}
private:
bool isRunning_;
};
_
workermanager.h:
_#include "worker.h"
class WorkerManager : public QObject
{
Q_OBJECT
public:
explicit WorkerManager(QObject *parent = 0) :
QObject(parent) {}
public slots:
void process()
{
QThread *thread = new QThread();
Worker *worker = new Worker();
connect(thread,SIGNAL(started()),worker,SLOT(process()));
connect(worker,SIGNAL(processingFinished()),this,SLOT(slot1()));
connect(worker,SIGNAL(inProgress()),this,SLOT(slot2()));
worker->moveToThread(thread);
qDebug() << "starting";
thread->start();
QThread::usleep(500);
while(worker->isRunning()) { }
qDebug() << "finished";
}
void slot1() { qDebug() << "slot1"; }
void slot2() { qDebug() << "slot2"; }
};
_
main.cpp(ケース1-
workerManager
の個別のスレッドはありません):
_#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerManager* workerManager = new WorkerManager;
workerManager->process();
qDebug() << "end";
return a.exec();
}
_
出力-_
slot1
_と_slot2
_の両方がa.exec()
で呼び出されます(???-メインイベントループを使用しますか?):
_starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end
slot2
slot2
slot2
slot2
slot2
slot1
_
main.cpp(ケース2-
workerManager
は別のスレッドに移動しましたが、スレッドは開始されていません):
_#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerManager* workerManager = new WorkerManager;
QThread *thread = new QThread();
workerManager->moveToThread(thread);
workerManager->process();
qDebug() << "end";
return a.exec();
}
_
出力-_
slot1
_も_slot2
_も呼び出されませんでした-(???スレッドに関連付けられたイベントループはシグナルを受信しますが、スレッドが開始されていないため、スロットは呼び出されませんか?):
_starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end
_
main.cpp(ケース3-
workerManager
が別のスレッドに移動し、スレッドが開始されましたが、workerManager::process()
がworkerManager->process()
を介して呼び出されました):
_#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerManager* workerManager = new WorkerManager;
QThread *thread = new QThread();
workerManager->moveToThread(thread);
thread->start();
workerManager->process();
qDebug() << "end";
return a.exec();
}
_
出力-
Worker
がまだprocess()
(???)を実行している間に_slot2
_が呼び出されました:
_starting
Worker(0x197bb20) processing started
slot2
slot2
slot2
slot2
Worker(0x197bb20) processing finished
finished
end
slot2
slot1
_
main.cpp(ケース4-
workerManager
は別のスレッドに移動し、スレッドは開始しましたが、workerManager::process()
はthread
からのstarted()
シグナルを使用して呼び出されました):
_#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerManager* workerManager = new WorkerManager;
QThread *thread = new QThread();
workerManager->moveToThread(thread);
QObject::connect(thread,SIGNAL(started()),workerManager,SLOT(process()));
thread->start();
qDebug() << "end";
return a.exec();
}
_
OUTPUT-
a.exec()
(???)に到達した後に処理されたすべてのイベント:
_end
starting
Worker(0x7f1d700013d0) processing started
Worker(0x7f1d700013d0) processing finished
finished
slot2
slot2
slot2
slot2
slot2
slot1
_
ご不明な点がありましたらありがとうございます。
得られたすべての結果は完全に正しいです。これがどのように機能するかを説明しようと思います。
イベントループは、システムイベントとユーザーイベントを処理するQtコードの内部ループです。 a.exec()
を呼び出すと、メインスレッドのイベントループが開始されます。別のスレッドのイベントループは、デフォルトで_QThread::run
_の実装で開始されます。
Qtは、イベントを処理する時期であると判断すると、イベントハンドラーを実行します。イベントハンドラーが機能している間、Qtは他のイベントを処理する機会がありません(QApplication::processEvents()
または他のメソッドによって直接指定されない限り)。イベントハンドラーが終了すると、制御フローはイベントループに戻り、Qtは別のハンドラーを実行して別のイベントを処理できます。
シグナルとスロットは、Qt用語のイベントおよびイベントハンドラーと同じではありません。ただし、スロットはイベントループによってある程度同様に処理されます。コードに制御フローがある場合(main
関数など)、他のC++関数と同じように任意のスロットをすぐに実行できます。しかし、Qtがそれを行う場合、それはイベントループからのみ行うことができます。スロットの実行が遅れる可能性がある一方で、信号は常にすぐに送信されることに注意してください。
次に、それぞれの場合に何が起こるかを見てみましょう。
_WorkerManager::process
_は、プログラムの開始時に直接実行されます。新しいスレッドが開始され、_Worker::process
_が新しいスレッドですぐに実行されます。 _WorkerManager::process
_は、ワーカーが完了するまで実行を継続し、メインスレッド内の他のすべてのアクション(スロット処理を含む)をフリーズします。 _WorkerManager::process
_が終了すると、制御フローは_QApplication::exec
_に移動します。 Qtは他のスレッドへの接続を確立し、スロットの呼び出しに関するメッセージを受信し、その結果、それらすべてを呼び出します。
Qtはデフォルトで、このオブジェクトが属するスレッド内のオブジェクトのスロットを実行します。メインスレッドは別のスレッドに属しているため、WorkerManager
のスロットを実行しません。ただし、このスレッドは開始されません。そのイベントループは決して終了しません。 _slot1
_および_slot2
_の呼び出しは、Qtのキューに永久に残され、スレッドの開始を待機します。悲しい話。
この場合、_WorkerManager::process
_はメインスレッドから直接呼び出すため、メインスレッドで実行されます。その間、WorkerManager
のスレッドが開始されます。そのイベントループが起動され、イベントを待機しています。 _WorkerManager::process
_はWorker
のスレッドを開始し、その中で_Worker::exec
_を実行します。 Worker
はWorkerManager
へのシグナルの送信を開始します。 WorkerManager
のスレッドは、ほぼ即座に適切なスロットの実行を開始します。この時点で、_WorkerManager::slot2
_と_WorkerManager::process
_が同時に実行されるのは厄介なようです。ただし、少なくともWorkerManager
がスレッドセーフである場合は、まったく問題ありません。 Worker
が完了した直後に、_WorkerManager::process
_が終了し、a.exec()
が実行されますが、処理するものはあまりありません。
メイン関数はWorkerManager
のスレッドを起動し、すぐにa.exec()
に移動します。その結果、出力の最初の行としてend
が生成されます。 a.exec()
は何かを処理し、プログラムの実行を保証しますが、別のスレッドに属しているため、WorkerManager
のスロットは実行しません。 _WorkerManager::process
_は、イベントループからWorkerManager
のスレッドで実行されます。 Worker
のスレッドが開始され、_Worker::process
_がWorker
のスレッドからWorkerManager
のスレッドへのシグナルの送信を開始します。残念ながら、後者は_WorkerManager::process
_の実行で忙しいです。 Worker
が完了すると、_WorkerManager::process
_も終了し、WorkerManager
のスレッドはキューに入れられたすべてのスロットをすぐに実行します。
コードの最大の問題は、usleep
と無限ループです。 Qtを使用する場合は、これらを使用しないでください。 _Worker::process
_でのスリープは、実際の計算の単なるプレースホルダーであることを理解しています。ただし、WorkerManager
からスリープと無限ループを削除する必要があります。 _WorkerManager::slot1
_を使用して、Worker
の終了を検出します。 GUIアプリケーションを開発する場合、WorkerManager
を別のスレッドに移動する必要はありません。そのすべてのメソッド(スリープなし)は高速に実行され、GUIをフリーズしません。