69

QThread の Qt ドキュメントには、QThread からクラスを作成し、run メソッドを実装するように記載されています。

以下は、4.7 Qthread ドキュメントからの抜粋です...

独自のスレッドを作成するには、QThread をサブクラス化し、run() を再実装します。例えば:

 class MyThread : public QThread
 {
 public:
     void run();
 };

 void MyThread::run()
 {
     QTcpSocket socket;
     // connect QTcpSocket's signals somewhere meaningful
     ...
     socket.connectToHost(hostName, portNumber);
     exec();
 }

したがって、私が作成したすべての単一のスレッドで、私はそれを実行しましたが、ほとんどの場合、問題なく動作します (どのオブジェクトにも moveToThread(this) を実装していませんが、うまく機能します)。

先週、問題が発生し (オブジェクトを作成した場所を回避することで問題を解決できました)、次のブログ投稿を見つけました。ここでは基本的に、QThread をサブクラス化することは実際には正しい方法ではない (そしてドキュメントが正しくない) ことを示しています。

これは Qt 開発者からのものなので、一見して興味があり、さらに考えてみると、彼に同意します。オブジェクト指向の原則に従って、クラスをサブクラス化してそのクラスをさらに強化したいだけです...クラスのメソッドを直接使用するだけではありません...それがインスタンス化する理由です...

カスタム QObject クラスをスレッドに移動したいとしましょう...それを行う「正しい」方法は何でしょうか? そのブログ投稿で、彼は「どこかに例がある」と言っています...しかし、誰かが私にそれをさらに説明できれば、それは大歓迎です!

アップデート:

この質問は非常に注目を集めているため、QThread を実装する「適切な」方法を含む 4.8 ドキュメントのコピー アンド ペーストを次に示します。

class Worker : public QObject
 {
     Q_OBJECT
     QThread workerThread;

 public slots:
     void doWork(const QString &parameter) {
         // ...
         emit resultReady(result);
     }

 signals:
     void resultReady(const QString &result);
 };

 class Controller : public QObject
 {
     Q_OBJECT
     QThread workerThread;
 public:
     Controller() {
         Worker *worker = new Worker;
         worker->moveToThread(&workerThread);
         connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
         connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString)));
         connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
         workerThread.start();
     }
     ~Controller() {
         workerThread.quit();
         workerThread.wait();
     }
 public slots:
     void handleResults(const QString &);
 signals:
     void operate(const QString &);
 };

Worker::workerThread不要であり、例では使用されていない余分なメンバーが含まれていることを指摘することは、依然として価値があると思います。その部分を削除すると、Qt でスレッド化を行う方法の適切な例になります。

4

5 に答える 5

31

私が追加することを考えることができる唯一のことは、QObjectsが単一のスレッドとの親和性を持っていることをさらに述べることです。これは通常、を作成するスレッドですQObject。したがってQObject、アプリのメインスレッドでを作成し、それを別のスレッドで使用する場合は、を使用moveToThread()してアフィニティを変更する必要があります。

QThreadこれにより、メソッドでオブジェクトをサブクラス化して作成する必要がrun()なくなり、コンテンツが適切にカプセル化された状態に保たれます。

そのブログ投稿には、例へのリンクが含まれています。かなり短いですが、基本的な考え方を示しています。を作成しQObject、シグナルを接続し、を作成し、QThreadに移動QObjectsQThreadてスレッドを開始します。信号/スロットメカニズムにより、スレッドの境界を適切かつ安全に越えることができます。

そのメカニズムの外部でオブジェクトのメソッドを呼び出す必要がある場合は、同期を導入する必要がある場合があります。

Qtには、スレッド以外にも、おそらく理解する価値のある優れたスレッド機能がいくつかあることは知っていますが、まだそうしていません:)

于 2010-11-04T02:51:16.747 に答える
10

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() をオーバーライドして、必要なものもすべて移動します。

于 2013-08-16T22:07:41.013 に答える
3

@ sergey-tachenovの優れた回答を損なうことはありませんが、Qt5ではSIGNALとSLOTの使用をやめ、コードを簡素化し、コンパイル時のチェックの利点を得ることができます:

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, ChildrenWatcher::exited,
            ChildProcesses::onChildExited);
    connect(childrenWatcher, ChildrenWatcher::signalled,
            ChildProcesses::onChildSignalled);
    connect(childrenWatcher, ChildrenWatcher::stateChanged,
            ChildProcesses::onChildStateChanged);
    // Make the watcher watch when the thread starts:
    connect(childrenWatcherThread, QThread::started,
            childrenWatcher, ChildrenWatcher::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, ChildProcesses::stopped,
            childrenWatcher, ChildrenWatcher::stop, Qt::DirectConnection);
    // Make the thread quit when the watcher self-destructs:
    connect(childrenWatcher, ChildrenWatcher::destroyed,
            childrenWatcherThread, QThread::quit);
    // Make the thread self-destruct when it finishes,
    // or rather, make the main thread delete it:
    connect(childrenWatcherThread, QThread::finished,
            childrenWatcherThread, QThread::deleteLater);
    childrenWatcherThread->start();
}
于 2014-04-27T08:39:15.113 に答える