安全でないマナー
Qtおよび基になるC++で生成されたコードの動作が未定義であるため、別のスレッドからジェネリックQWidgetメソッドを直接呼び出すことは安全ではありません。それは核の先制攻撃を開始するかもしれません。あなたは警告されました。QWidget::update()
したがって、Qtが提供する安全なスレッド間通信を使用しない限り、別のスレッドから、、、QLabel::setText(...)
またはその他のそのようなメソッドを呼び出すことはできません。
間違い
// in a separate thread
widget->update();
widget->setText("foo");
右
// in a separate thread
// using QMetaObject::invokeMethod(widget, "update") is unoptimal
QCoreApplication::postEvent(widget, new QEvent(QEvent::UpdateRequest),
Qt::LowEventPriority);
QMetaObject::invokeMethod(widget, "setText", Q_ARG(QString, "foo"));
スレッド間通信について
一部のQObjectを含むスレッドと通信する簡単な方法には、静的QCoreApplication::postEvent()
メソッドを介してスレッドにイベントを投稿することが含まれます。GUIスレッドは、多数のQObjectが存在するスレッドの一般的な例です。使用postEvent()
は常にスレッドセーフです。このメソッドは、どのスレッドから呼び出されたかをまったく気にしません。
内部でを使用するさまざまな静的メソッドがQt全体に散在していますpostEvent()
。彼らの調号は、彼らも静的になるということです!家族はそのQMetaObject::invokeMethod(...)
ような例です。
異なるスレッドに存在するQObjectのスロットに信号を接続する場合、キュー接続が使用されます。このような接続では、信号が発信されるたびQMetaCallEvent
に、-が正しく推測された-を使用して、aが作成され、ターゲットQObjectに送信されますQCoreApplication::postEvent()
。
したがって、GUI以外のスレッドのQObjectからQWidget::update()
スロットに信号を接続することは安全です。これは、内部的にそのような接続がpostEvent()
信号をスレッドバリアを越えて伝播するために使用するためです。QWidget::update()
GUIスレッドのイベントループはそれを取得し、ラベル上のへの実際の呼び出しを実行します。完全にスレッドセーフ。
最適化
スレッド間でシグナルスロット接続またはメソッド呼び出しを使用する場合の唯一の問題は、Qtが更新を圧縮する必要があることを認識していないことです。別のスレッドに接続されたシグナルを送信するQWidget::update()
たび、または呼び出すたびinvokeMethod
に、イベントキューにイベントが投稿されます。
文書化されていない場合、スレッド間更新を行う慣用的な方法は次のとおりです。
QApplication::postEvent(widget,
new QEvent(QEvent::UpdateRequest),
Qt::LowEventPriority);
UpdateRequestイベントはQtによって圧縮されます。いつでも、特定のウィジェットのイベントキューにそのようなイベントを1つだけ含めることができます。したがって、最初にそのようなイベントを投稿すると、ウィジェットが実際にイベントを消費してそれ自体を再描画(更新)するまで、後続のイベントは無視されます。
では、たとえば、さまざまなウィジェット設定呼び出しについてはどうQLabel::setText()
でしょうか。どういうわけかそれらも圧縮できたら、きっとクールだろうか?はい、それは確かに可能ですが、パブリックQtインターフェイスに限定する必要があります。Qtはここでは明白なことを何も提供していません。
QMetaCallEvent
重要なのは、別のイベントを投稿する前に、ウィジェットのイベントキューから保留中のイベントを削除することです。これは、特定のウィジェットへのキューに入れられたシグナルスロット接続が私たちからのみ来ていることが確実な場合にのみ安全です。GUIスレッド内のすべてが自動接続を使用し、GUIスレッドのイベントキューにイベントを送信せずにスロットを直接呼び出すようにデフォルト設定されるため、これは通常、安全な仮定です。
方法は次のとおりです。
QCoreApplication::removePostedEvents(label, QEvent::MetaCall);
QMetaObject::invokeMethod(label, "setText", Q_ARG(QString, "foo"));
パフォーマンス上の理由から、スロットの正規化された表現を使用していることに注意してください。余分なスペースはなく、すべてconst Type &
が単純に変換されますType
。
注:私自身のプロジェクトでは、プライベートQtイベント圧縮APIを使用することがありますが、ここではそれを推奨しません。
追記
Cコードは呼び出すべきではありませんQObject::connect
、私はそれが悪いデザインの兆候だと思います。CコードからQtコードに通信する場合は、静的コードを使用しますQCoreApplication::postEvent(...)
(もちろん、Cに適切にラップ/公開されます)。他の方法で通信するために、スレッドでイベントキューを手動でコーディングできます。少なくとも、「イベントキュー」は固定構造になりますが、重要なのは、ミューテックスを使用してアクセスを保護することです。
悪い設計のもう1つの兆候は、GUIクラスの外部のコードが、そのクラス自体の個々のUIアイテムに依存していることです。UIクラス全体(たとえば、)に一連のシグナルとスロットを提供し、MainWindow
それらを関連するUI要素に内部的に転送する必要があります。connect()
信号を送信することはできますが、スロットをスロットに送信することはできません。転送スロットを手動でコーディングする必要があります。