web-dev-qa-db-ja.com

QThreadを実装する正しい方法は何ですか...(例をご覧ください...)

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開発者からのものであるため、一見したところ興味があり、さらに熟考した上で彼に同意します。 OO原則に従って、クラスをさらにサブクラス化してクラスをさらに強化したいだけです...クラスメソッドを直接使用するのではなく...インスタンス化する理由は...

カスタム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でのスレッド化の適切な例になります。

61
g19fanatic

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

これにより、QThreadをサブクラス化する必要がなくなり、run()メソッドでオブジェクトを作成する必要がなくなります。そのため、内容を適切にカプセル化できます。

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

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

Qtには、おそらく他のニース スレッド化機能 に精通する価値があるスレッドを超えたものがあることは知っていますが、まだそうしていません:)

31
Arnold Spence

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つのシグナルを接続して、マネージャーにその子プロセス(終了/シグナル/ god-knows-what-happened)の運命について通知します。次に、主な楽しみを開始します。

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つの接続を意味するだけです。他の目的のためにfinish()シグナルを必要としないため、ワーカー自体からdeleteLater()を呼び出すことを選択しました。

Mayaはまた、ワーカーのコンストラクターに新しいQObjectを割り当てるべきではないことにも言及しています。これらは、ワーカーを移動するスレッドに存在しないためです。とにかくそれを行うと言うでしょうOOP動作します。これらのすべてのQObjectsがワーカーの子であることを確認してください(つまり、QObject(QObject *)コンストラクターを使用します)-moveToThread(オブジェクトの子ではないQObjectが本当に必要な場合は、ワーカーのmoveToThread()をオーバーライドして、必要なものもすべて移動します。

9
Sergei Tachenov

@ 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();
}
3
parsley72

qthreadクラスをサブクラス化しても、元のスレッドでコードが実行されます。 GUIスレッド(メインスレッド)を既に使用しているアプリケーションでudpリスナーを実行したかったのですが、udpリスナーが完全に動作している間、サブクラスのqthreadイベントハンドラーによってブロックされたため、GUIがフリーズしました。 g19fanaticが投稿した内容は正しいと思いますが、オブジェクトを新しいスレッドに正常に移行するにはワーカースレッドも必要です。 this QTのスレッドのDoとDonの詳細を説明する投稿を見つけました。

QThreadをサブクラス化する前に必ずお読みください!

2
Zaid