QThread を正しく使用する方法の一例を次に示しますが、コメントに反映されているいくつかの問題があります。特に、スロットが実行される順序は厳密に定義されていないため、さまざまな問題が発生する可能性があります。2013 年 8 月 6 日に投稿されたコメントは、この問題に対処するための優れたアイデアを提供します。私は自分のプログラムでそのようなものを使用しています。明確にするためのコード例を次に示します。
基本的な考え方は同じです。メイン スレッドに QThread インスタンスを作成し、作成した新しいスレッドにワーカー クラス インスタンスを作成してから、すべてのシグナルを接続します。
void ChildProcesses::start()
{
QThread *childrenWatcherThread = new QThread();
ChildrenWatcher *childrenWatcher = new ChildrenWatcher();
childrenWatcher->moveToThread(childrenWatcherThread);
// These three signals carry the "outcome" of the worker job.
connect(childrenWatcher, SIGNAL(exited(int, int)),
SLOT(onChildExited(int, int)));
connect(childrenWatcher, SIGNAL(signalled(int, int)),
SLOT(onChildSignalled(int, int)));
connect(childrenWatcher, SIGNAL(stateChanged(int)),
SLOT(onChildStateChanged(int)));
// Make the watcher watch when the thread starts:
connect(childrenWatcherThread, SIGNAL(started()),
childrenWatcher, SLOT(watch()));
// Make the watcher set its 'stop' flag when we're done.
// This is performed while the watch() method is still running,
// so we need to execute it concurrently from this thread,
// hence the Qt::DirectConnection. The stop() method is thread-safe
// (uses a mutex to set the flag).
connect(this, SIGNAL(stopped()),
childrenWatcher, SLOT(stop()), Qt::DirectConnection);
// Make the thread quit when the watcher self-destructs:
connect(childrenWatcher, SIGNAL(destroyed()),
childrenWatcherThread, SLOT(quit()));
// Make the thread self-destruct when it finishes,
// or rather, make the main thread delete it:
connect(childrenWatcherThread, SIGNAL(finished()),
childrenWatcherThread, SLOT(deleteLater()));
childrenWatcherThread->start();
}
背景:
ChildProcesses クラスは、spawn() 呼び出しで新しい子プロセスを開始したり、現在実行中のプロセスのリストを保持したりする子プロセス マネージャーです。ただし、子の状態を追跡する必要があります。つまり、Linux では waitpid() 呼び出しを使用し、Windows では WaitForMultipleObjects を使用します。以前はタイマーを使用して非ブロック モードでこれらを呼び出していましたが、今はより迅速な反応、つまりブロック モードが必要です。そこでスレッドの出番です。
ChildrenWatcher クラスは次のように定義されています。
class ChildrenWatcher: public QObject {
Q_OBJECT
private:
QMutex mutex;
bool stopped;
bool isStopped();
public:
ChildrenWatcher();
public slots:
/// This is the method which runs in the thread.
void watch();
/// Sets the stop flag.
void stop();
signals:
/// A child process exited normally.
void exited(int ospid, int code);
/// A child process crashed (Unix only).
void signalled(int ospid, int signal);
/// Something happened to a child (Unix only).
void stateChanged(int ospid);
};
これがどのように機能するかです。これらすべてが開始されると、ChildProcess::start() メソッドが呼び出されます (上記を参照)。新しい QThread と新しい ChildrenWatcher が作成され、新しいスレッドに移動されます。次に、マネージャーに子プロセスの運命を知らせる 3 つのシグナルを接続します (終了/シグナル/神のみぞ知る)。その後、メインの楽しみが始まります。
QThread::started() を ChildrenWatcher::watch() メソッドに接続して、スレッドの準備が整うとすぐに開始されるようにします。ウォッチャーは新しいスレッドに存在するため、そこで watch() メソッドが実行されます (キュー接続を使用してスロットを呼び出します)。
次に、非同期で行う必要があるため、Qt::DirectConnection を使用して ChildProcesses::stopped() シグナルを ChildrenWatcher::stop() スロットに接続します。これは、ChildProcesses マネージャーが不要になったときにスレッドが停止するために必要です。stop() メソッドは次のようになります。
void ChildrenWatcher::stop()
{
mutex.lock();
stopped = true;
mutex.unlock();
}
そして ChildrenWatcher::watch():
void ChildrenWatcher::watch()
{
while (!isStopped()) {
// Blocking waitpid() call here.
// Maybe emit one of the three informational signals here too.
}
// Self-destruct now!
deleteLater();
}
ああ、isStopped() メソッドは、while() 条件でミューテックスを使用する便利な方法です。
bool ChildrenWatcher::isStopped()
{
bool stopped;
mutex.lock();
stopped = this->stopped;
mutex.unlock();
return stopped;
}
つまり、終了する必要があるときに停止フラグを設定すると、次に isStopped() が呼び出されたときに false が返され、スレッドが終了します。
では、watch() ループが終了するとどうなるでしょうか。これは deleteLater() を呼び出すので、deleteLater() 呼び出しの直後 (watch() が戻るとき) に制御がスレッド イベント ループに戻るとすぐに、オブジェクトは自己破壊します。ChildProcesses::start() に戻ると、ウォッチャーの destroy() シグナルからスレッドの quit() スロットへの接続があることがわかります。これは、ウォッチャーが完了すると、スレッドが自動的に終了することを意味します。そして、終了すると、自身の finished() シグナルが deleteLater() スロットに接続されているため、自己破壊もします。
これは Maya が投稿したアイデアとほとんど同じですが、私は自己破壊イディオムを使用しているため、スロットが呼び出される順序に依存する必要はありません。それは常に最初に自己破壊し、後でスレッドを停止し、その後自己破壊します。ワーカーで finished() シグナルを定義し、それを独自の deleteLater() に接続することもできますが、それは接続が 1 つ増えることを意味します。他の目的で finished() シグナルは必要ないので、ワーカー自体から deleteLater() を呼び出すことにしました。
Maya はまた、新しい QObjects をワーカーのコンストラクターに割り当てるべきではないと述べています。なぜなら、新しい QObjects はワーカーの移動先のスレッドには存在しないからです。それがOOPの仕組みだからです。これらすべての QObjects がワーカーの子であることを確認してください (つまり、QObject(QObject*) コンストラクターを使用します) - moveToThread() は、移動されるオブジェクトとともにすべての子を移動します。オブジェクトの子ではない QObjects が本当に必要な場合は、ワーカーで moveToThread() をオーバーライドして、必要なものもすべて移動します。