2016-11-01 19 views
1

最近QTフレームワークを使い始めました。昨日、私は単純なマルチスレッドアプリケーションをプログラミングし始めました。現時点では、私は次の問題に多少なりとも取り組んでいます。Qtがイベントループをブロックしました

「重い計算」を行うためにスレッドを使用する2つのワーカークラスを考えてみましょう。最初のクラスFooWorkerは次のようになります。

class FooWorker : public QObject 
{ 
    Q_OBJECT 

public: 
    FooWorker() : QObject() { } 
    ~FooWorker() { } 

signals: 
    void notify(int); 
    void aborted(); 

public slots: 
    void doWork() 
    { 
     int counter = 0; 
     forever { 

      // For the sake of this example this reassembles a heavy computational process 
      if(counter++ < 10) { 
       emit notify(counter); 
       QThread::sleep(1); 
      } else { 
       counter = 0; 

       // Wait until we get a signal to restart the process 
       mutex_.lock(); 
       condition_.wait(&mutex_); 
       mutex_.unlock(); 
      } 
      // We should check for a cancellation flag every iteration... 
     } 

     emit aborted(); 
    } 

private: 
    QMutex mutex_; 
    QWaitCondition condition_; 
}; 

スロット 'doWork'は別のスレッドで実行するようにスケジュールされます。スロットは永遠に走り、10の通知が発せられるまで毎秒信号を発信しています。その後、もう一度目が覚めるまで待つ。

第二のクラス、BarWorkerは、次のようになります。

class BarWorker : public QObject 
{ 
    Q_OBJECT 

public: 
    BarWorker() : QObject() { } 
    ~BarWorker() { } 

signals: 
    void aborted(); 

public slots: 
    void doWork() 
    { 
     forever { 
      // Another heavy computational process 
      QThread::sleep(1); 

      // We should check for a cancellation flag every iteration... 
     } 

     emit aborted(); 
    } 

    void onNotify(int value) 
    { 
     qDebug() << "Notification value:" << value; 
    } 
}; 

再びスロット「doWorkは」別のスレッドで実行するようにスケジュールされます。スロットは永遠に実行され、重い計算プロセスを実行します。再びプロセスが完了すると、私たちはそれが再び起き上がるまで待つ(この例のために、私はこのクラスでそれを残した)。

は最後のメインは、次のようになります。

int main(int argc, char *argv[]) 
{ 
    QCoreApplication a(argc, argv); 

    QThread* barThread = new QThread(); 
    BarWorker* barWorker = new BarWorker(); 
    barWorker->moveToThread(barThread); 

    QThread* fooThread = new QThread(); 
    FooWorker* fooWorker = new FooWorker(); 
    fooWorker->moveToThread(fooThread); 

    // Automatically deletes worker and thread 
    QObject::connect(fooThread, SIGNAL(started()), fooWorker, SLOT(doWork())); 
    QObject::connect(fooWorker, SIGNAL(aborted()), fooThread, SLOT(quit())); 
    QObject::connect(fooWorker, SIGNAL(aborted()), fooWorker, SLOT(deleteLater())); 
    QObject::connect(fooThread, SIGNAL(finished()), fooThread, SLOT(deleteLater())); 

    QObject::connect(barThread, SIGNAL(started()), barWorker, SLOT(doWork())); 
    QObject::connect(barWorker, SIGNAL(aborted()), barThread, SLOT(quit())); 
    QObject::connect(barWorker, SIGNAL(aborted()), barWorker, SLOT(deleteLater())); 
    QObject::connect(barThread, SIGNAL(finished()), barThread, SLOT(deleteLater())); 

    QObject::connect(fooWorker, SIGNAL(notify(int)), barWorker, SLOT(onNotify(int)), Qt::QueuedConnection); 

    fooThread->start(); 
    barThread->start(); 

    return a.exec(); 
} 

私は、アプリケーションは何も実行しない

が印刷されます。これは、BarWorkerインスタンスのイベントループがブロックされているために予想されていました。 「通知」信号が放射されると、「onNotify」スロットはイベントキューに入れられます。 'doWork'スロットには決してループを終了しないので( '手動で中止するまで)、' onNotify 'スロットは呼び出されません。これを解決するために、私はつまり、物事のカップルを行うことができます。

  1. はQtの:: DirectConnectionフラグを使用して「onNotify」スロットへの「通知」信号を接続します。これは、シグナルスレッド上で実行される通常の関数呼び出しのように見えます。
  2. 時々、QCoreApplication::processEvents()メソッドを呼び出して、イベントキューを強制的に処理します。
  3. 未知の解決策私は現時点では分かりません:)???

解決策の上にあるIMHOは多少醜いものであり、正しく感じられないので、誰かがこの問題の代替解決策を持っていることを望みます。

+0

「永遠」とは何ですか? – HazemGomaa

+1

Qtの無限ループ用です。http://doc.qt.io/qt-5/qtglobal.html#forever – Naidu

+0

@PAVANCHANDAKAありがとう! – HazemGomaa

答えて

5

ここには「魔法の」解決策はありません。独自のカスタムイベントループを実行している場合、スレッドはQtのイベントループを実行することはできません。

  1. コールでprocessEvents()定期的にイベントループから、あなたはあなたの質問で提案されているように、Qtのイベントハンドリングように:実際には、実際には同じコインの表裏二つの一般的な解決策があり、コードが実行され、着信する非同期信号を処理することがあります。

  2. doWork()メソッドで長時間実行されるループを持たないでください。代わりに、Qtimer :: singleShot(0、this、SLOT(doWork()))のようなものを呼び出すと、Qtimer :: singleShotのようなものを呼び出すことができます。 doWork()の最初の呼び出しが返ってくるとすぐにdoループ()メソッドが再び呼び出されます。このようにして、Qtイベント・ループは、単一のdoWork()コールで取り込まれた(短い)期間よりも長く保持されることはありません。これら二つのオプションの

、私はそれが通常のやり方で実行するようにQtのイベントループを可能にし、それはまた、潜在的なトリップ・オーバー・自分で自分-靴ひもの問題を避けるため、2番目は、望ましいと思います - - 例えばソリューション(1)を使用しているときにprocessEvents()を呼び出すと、BarWorkerオブジェクトを削除するスロットが呼び出されます。 processEvents()呼び出しが戻ると、BarWorker :: doWork()は実行を再開しますが、その時点で、ローカルメンバー変数と通常の実行の一部としてアクセスできる仮想メソッドはすべて破棄され、それらを読み書きします未定義の動作が発生します(運が良ければ、デバッグが簡単です)。 doWork()への呼び出しの間にBarWorkerオブジェクトが削除された場合、doWork()へのキューに入れられた非同期呼び出しは安全にキャンセルされるため、solution(2)を使用するとそのような可能性はありません。

1

イベントループと相互運用するforeverループのイディオムはゼロ継続時間タイマーです。

// https://github.com/KubaO/stackoverflown/tree/master/questions/worker-timer-40369716 
#include <QtCore> 

// See http://stackoverflow.com/q/40382820/1329652 
template <typename Fun> void safe(QObject * obj, Fun && fun) { 
    Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread()); 
    if (Q_LIKELY(obj->thread() == QThread::currentThread())) 
     return fun(); 
    struct Event : public QEvent { 
     using F = typename std::decay<Fun>::type; 
     F fun; 
     Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {} 
     Event(const F & fun) : QEvent(QEvent::None), fun(fun) {} 
     ~Event() { fun(); } 
    }; 
    QCoreApplication::postEvent(
      obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun))); 
} 

class WorkerBase : public QObject { 
    Q_OBJECT 
    QBasicTimer timer_; 
protected: 
    virtual void workUnit() = 0; 
    void timerEvent(QTimerEvent *event) override { 
     if (event->timerId() == timer_.timerId() && timer_.isActive()) 
      workUnit(); 
    } 
public: 
    using QObject::QObject; 
    Q_SIGNAL void finished(); 
    /// Thread-safe 
    Q_SLOT void virtual start() { 
     safe(this, [=]{ 
      timer_.start(0, this); 
     }); 
    } 
    /// Thread-safe 
    Q_SLOT void virtual stop() { 
     safe(this, [=]{ 
      if (!isActive()) return; 
      timer_.stop(); 
      emit finished(); 
     }); 
    } 
    bool isActive() const { return timer_.isActive(); } 
    ~WorkerBase() { 
     if (isActive()) emit finished(); 
    } 
}; 

労働者は次になる:我々は、作業単位がworkUnit方法で行われるべきであるWorkerBaseクラスにそれを考慮することができる

class FooWorker : public WorkerBase 
{ 
    Q_OBJECT 
    int counter = 0; 
    bool isDone() const { return counter >= 10; } 
    void workUnit() override { 
     if (!isDone()) { 
      counter ++; 
      emit notify(counter); 
      QThread::sleep(1); 
     } else 
      stop(); 
    } 
public: 
    void start() override { 
     counter = 0; 
     WorkerBase::start(); 
    } 
    void stop() override { 
     if (!isDone()) emit aborted(); 
     WorkerBase::stop(); 
    } 
    Q_SIGNAL void notify(int); 
    Q_SIGNAL void aborted(); 
}; 

class BarWorker : public WorkerBase 
{ 
    Q_OBJECT 
    void workUnit() override { 
     QThread::sleep(1); 
    } 
public: 
    void stop() override { 
     emit aborted(); 
     WorkerBase::stop(); 
    } 
    Q_SIGNAL void aborted(); 
    Q_SLOT void onNotify(int value) 
    { 
     qDebug() << "Notification value:" << value; 
    } 
}; 

なおaborted()finished()信号異なる意味を持つ。

最後に、テストハーネス:

class Thread : public QThread { public: ~Thread() { quit(); wait(); } }; 

int main(int argc, char ** argv) { 
    QCoreApplication app{argc, argv}; 

    BarWorker barWorker; 
    FooWorker fooWorker; 
    Thread barThread, fooThread; 
    barWorker.moveToThread(&barThread); 
    fooWorker.moveToThread(&fooThread); 
    barWorker.start(); 
    fooWorker.start(); 

    QObject::connect(&fooWorker, &FooWorker::finished, &app, &QCoreApplication::quit); 
    QObject::connect(&fooWorker, &FooWorker::notify, &barWorker, &BarWorker::onNotify); 

    fooThread.start(); 
    barThread.start(); 
    return app.exec(); 
} 

#include "main.moc" 

あなたがQBasicTimer::stop: Failed. Possibly trying to stop from a different thread警告が出た場合は、それは全く重要でだとQtのバグの結果です。

関連する問題