web-dev-qa-db-ja.com

Qt5:スレッドでシグナルを待つ方法は?

おそらく、タイトルの質問はあまり明確ではありません。 Windows7でQt5を使用しています。

ある時点のスレッド(QThread)、"process()"関数/メソッドでは、このスレッドで使用しているQSslSocketに属する"encrypted()" SIGNALを待つ必要があります。また、無限ループでブロックされないようにするために、QTimerを使用して"timeout()" SIGNALを待つ必要があると思います...
私が今持っているものは:

_// start processing data
void Worker::process()
{
    status = 0;
    connect(sslSocket, SIGNAL(encrypted()), this, SLOT(encryptionStarted()));
    QTimer timer;
    connect(&timer, SIGNAL(timeout()), this, SLOT(timerTimeout()));
    timer.start(10000);
    while(status == 0)
    {
        QThread::msleep(5);
    }

    qDebug("Ok, exited loop!");

    // other_things here
    // .................
    // end other_things

    emit finished();
}

// slot (for timer)
void Worker::timerTimeout()
{
    status = 1;
}

// slot (for SSL socket encryption ready)
void Worker::encryptionStarted()
{
    status = 2;
}
_

まあ、明らかにそれは動作しません。それはそのwhileループに永遠にとどまります...
それで、質問は次のとおりです。この問題を解決する方法はありますか? "encrypted()" SIGNALを待つにはどうすればいいですか?待機ループ/スレッドでスタックするのを避けるために、10秒としましょう。

15

ローカルイベントループを使用して、信号が発信されるのを待つことができます。

QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect( sslSocket, &QSslSocket::encrypted, &loop, &QEventLoop::quit );
connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );
timer.start(msTimeout);
loop.exec();

if(timer.isActive())
    qDebug("encrypted");
else
    qDebug("timeout");

ここでは、encryptedが発行されるか、タイムアウトに達するまで待機します。

34
Nejat

非同期プログラミングでは、「待機」はアンチパターンと見なされます。物事を待つ代わりに、条件が満たされることに反応するようにコードを設計します。たとえば、コードを信号に接続します。

これを実装する1つの方法は、アクションを個別の状態にスライスし、各状態が開始されたときにいくつかの作業を行うことです。もちろん、作業量が自明でない場合は、ラムダの代わりに別のスロットを使用して、読みやすくします。

明示的なメモリ管理がないことに注意してください。 Qtクラスへの所有ポインターの使用は時期尚早な最適化であり、不必要な場合は避けるべきです。オブジェクトは、Worker(またはその [〜#〜] pimpl [〜#〜] )の直接のメンバーにすることができます。

サブオブジェクトはすべて、ルートにWorkerを持つ所有権階層の一部である必要があります。そうすれば、Workerインスタンスを別のスレッドに安全に移動でき、使用するオブジェクトがそれに続きます。もちろん、正しいスレッドでWorkerをインスタンス化することもできます-そのための簡単な idiom があります。スレッドのイベントディスパッチャーはワーカーを所有しているため、スレッドのイベントループが終了すると(つまり、QThread::quit()を呼び出した後)、ワーカーは自動的に破棄され、リソースはリークしません。

_template <typename Obj>
void instantiateInThread(QThread * thread) {
  Q_ASSERT(thread);
  QObject * dispatcher = thread->eventDispatcher();
  Q_ASSERT(dispatcher); // the thread must have an event loop
  QTimer::singleShot(0, dispatcher, [dispatcher](){
    // this happens in the given thread
    new Obj(dispatcher);
  });
}
_

ワーカーの実装:

_class Worker : public QObject {
  Q_OBJECT
  QSslSocket sslSocket;
  QTimer timer;
  QStateMachine machine;
  QState s1, s2, s3;
  Q_SIGNAL void finished();
public:
  explicit Worker(QObject * parent = {}) : QObject(parent),
    sslSocket(this), timer(this), machine(this),
    s1(&machine), s2(&machine), s3(&machine) {
    timer.setSingleShot(true);
    s1.addTransition(&sslSocket, SIGNAL(encrypted()), &s2);
    s1.addTransition(&timer, SIGNAL(timeout()), &s3);
    connect(&s1, &QState::entered, [this]{
      // connect the socket here
      ...
      timer.start(10000);
    });
    connect(&s2, &QState::entered, [this]{
      // other_things here
      ...
      // end other_things
      emit finished();
    });
    machine.setInitialState(&s1);
    machine.start();
  }
};
_

次に:

_void waitForEventDispatcher(QThread * thread) {
  while (thread->isRunning() && !thread->eventDispatcher())
    QThread::yieldCurrentThread();
}

int main(int argc, char ** argv) {
  QCoreApplication app{argc, argv};
  struct _ : QThread { ~Thread() { quit(); wait(); } thread;
  thread.start();
  waitForEventDispatcher(&thread);
  instantiateInThread<Worker>(&myThread);
  ...
  return app.exec();
}
_

QThread::started()への接続は際どいことに注意してください。イベントディスパッチャーは、QThread::run()内のコードが実行されるまで存在しません。したがって、スレッドが譲歩するのを待つ必要があります-これは、ワーカースレッドが1つまたは2つの譲歩内で十分に進行する可能性が非常に高いです。したがって、多くの時間を無駄にしません。

7
Kuba Ober

最近少し時間があり、調査をしました...
まあ、「 http://doc.qt.io/qt-5/qsslsocket.html 」を閲覧して、これを見つけました:

bool QSslSocket::waitForEncrypted(int msecs = 30000)

私の本当の恥に、私は前に気づかなかった... :(
必ずメガネを購入する必要があります(残念ながら、冗談ではありません!)
それをテストするために、それに応じてコードを修正します(月曜日@オフィス)。
動作する可能性はかなり高いです。あなたは何と言います:それは仕事をしますか?
はい、私の質問に答えるのはちょっと変ですが、たぶんIS解決策なので、共有することにしました:)