6

以下の例 (Qt GUI アプリケーション内) では、新しいスレッドが開始されます (いくつかの作業を実行するイベント ループを使用)。

void doWork()
{
   QThread* workerThread = new QThread();

   Worker* worker = new Worker();
   worker->moveToThread(workerThread);

   connect(workerThread, SIGNAL(started()), worker, SLOT(startWork()));
   connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));

   connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
   connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));

   workerThread->start();
}

startWork()実行時間の長い操作で、その間にアプリケーションを閉じることができます。

startWork()上で が実行されている限り、アプリケーションが閉じられないことを期待していましたworkerThread。ただし、最後のアプリケーション ウィンドウを閉じると、workerThread(長時間実行されている操作中に) 瞬時に消え、アプリケーションは問題なく終了するようです。

疑問が生じました:

  1. なぜworkerThreadすぐにワイプされたのですか?
    • 親子の問題でしょうか?
    • Qtはそのような状況をどのように処理しますか?
  2. wait()QThreadを(最終的に) 呼び出さないのはプログラマーの間違いですか?
    • たとえそうであっても、私はwait()スロットの中に入れようとしましたaboutToQuit()が、長時間実行された操作が行われた後、アプリケーションは閉じられませんでした(上記の設定で)。quit(); wait();(前述のスロット内で)のみ、アプリケーションを閉じることができました。なんで?
4

2 に答える 2

7

QThread基本的に、長年の API バグがあります。常に破壊可能な状態にあるとは限りません。C++ では、デストラクタを安全に呼び出すことができる場合、オブジェクトは破壊可能な状態にあると見なされます。ランニングの破壊QThreadはエラーです。AQThreadは単なるスレッド コントローラであり、「スレッド」自体ではありません。がどのように機能するかを考えてみてくださいQFile。開いているかどうかに関係なく、いつでも破壊できます。ファイルの概念をリソースとして真にカプセル化します。AQThreadは、ネイティブ (システム) スレッドのラッパーとしては薄すぎます。それを破棄すると、ネイティブ スレッドが存在する場合でも、それを終了したり破棄したりしません。これはリソース リーク (スレッドは OS リソースです) であり、人々はこの問題に何度もつまずきます。

アプリケーションのmain()関数が戻ると、C/C++ ランタイム ライブラリの実装によってアプリケーションのすべてのスレッドが終了し、事実上アプリケーション全体が終了します。これがあなたが望む行動であるかどうかはあなた次第です。あなたは、イベントループを実行しているスレッドであるはずquit()ですwait()。イベント ループのないスレッドの場合、quit()はノーオペレーションであり、独自の終了フラグを実装する必要があります。スレッドを破棄する前に、スレッドをオンにする必要 があります。wait()これは、競合状態を防ぐためです。

以下は の安全なラッパーですQThread。再実装できないため、これは final クラスですrun。これは重要です。なぜなら、run の再実装は何もしないような方法で実行され、クラスのコントラクトをquit破る可能性があるからです。

#include <QThread>
#include <QPointer>

class Thread : public QThread {
   using QThread::run; // final
public:
   Thread(QObject * parent = 0) : QThread(parent) {}
   ~Thread() { quit(); wait(); }
};

class ThreadQuitter {
public:
   typedef QList<QPointer<Thread>> List;
private:
   List m_threads;
   Q_DISABLE_COPY(ThreadQuitter)
public:
   ThreadQuitter() {}
   ThreadQuitter(const List & threads) : m_threads(threads) {}
   ThreadQuitter(List && threads) : m_threads(std::move(threads)) {}
   ThreadQuitter & operator<<(Thread* thread) { 
     m_threads << thread; return *this;
   }
   ThreadQuitter & operator<<(Thread& thread) {
     m_threads << &thread; return *this;
   }
   ~ThreadQuitter() {
      foreach(Thread* thread, m_threads) thread->quit();
   }
};

次のように使用できます。

#include <QCoreApplication>

int main(int argc, char ** argv) {
   QCoreApplication app(argc, argv);
   QObject worker1, worker2;
   Thread thread1, thread2;
   // Style 1
   ThreadQuitter quitter;
   quitter << thread1 << thread2;
   // Style 2
   ThreadQuitter quitterB(ThreadQuitter::List() << &thread1 << &thread2);
   //
   worker1.moveToThread(&thread1);
   worker2.moveToThread(&thread2);
   thread1.start();
   thread2.start();

   QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);
   return app.exec();
}

から戻るmainと、スレッドはquit()すべてのワーカー スレッドを終了します。これにより、スレッドを並行して巻き上げることができます。次に、thread2.~Threadそのスレッドが終了するのを待ってからthread1.~Thread、同じことを行います。スレッドはなくなり、オブジェクトはスレッドレスになり、安全に破棄できます。worker2.~QObject最初に が呼び出され、続いてworker1.~QObject.

于 2013-10-29T18:27:42.153 に答える