3

次の問題があります。メインアプリケーションは、ウィンドウとユーザーインタラクションを表示するためにQtツールキットを使用しています。ただし、アプリケーションの大部分はGUI部分を認識していません。次のデザインを作成しました。

  • 特定のオブジェクトのレンダリングを要求する可能性のあるシングルトンクラスがあります(OpenSceneGraphノード。ただし、これは質問には関係ありません)。
  • レンダリング要求により、シングルトンは信号を送信します
  • メインウィンドウクラス(Qtを使用)には、オブジェクトのレンダリングを処理するためのスロットがあります
  • 現在、スロットは新しいテキスト編集ウィジェットを作成し、それをQMdiAreaメインウィンドウのに配置するだけです。

ただし、新しいウィジェットを作成しようとすると、必然的にアプリケーションがクラッシュします。エラーメッセージ領域:

QObject::setParent: Cannot set parent, new parent is in a different thread
[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
myApplication: ../../src/xcb_io.c:178: dequeue_pending_request: Assertion `!xcb_xlib_unknown_req_in_deq' failed.
Aborted

スタックオーバーフローを熟読した後、私は同様の質問を見つけました(これはこの状況にはすぐには当てはまりませんでした)。明らかに、メインウィンドウの何かを別のスレッドから変更するとき、Qtはそれを気に入らない。しかし、私は意識的に新しいスレッドを作成しなかったので、シングルトン(の呼び出しの直後にmain関数で作成されるQApplication())はQtと同じスレッドにあるべきだと思いました。どうやら、私は間違っています。

これは私が行っていることを示す最小限の例です(コードの関連部分を抽出したので、例は正確には機能しません):

class Object
{
public:
};

class Singleton
{
public:
  typedef boost::signals2::signal<void (Object*)> signalShowObject;
  signalShowObject _showObject;
};

class MainWindow : public QMainWindow
{
public:
  MainWindow()
  {
    Singleton::getInstance()->_showObject.connect( boost::bind(&MainWindow::showObject, this, _1) );

    // Set up MDI area etc.
  }

private:
  QMdiArea* _mdiArea;

  void showObject(Object* object)
  {
    // Creating a new subwindow here causes the crash. The `object` pointer is
    // not used and has just been included because it models my real problem
    // better.
    _mdiArea->addSubWindow( new QTextEdit() )->show();
  }
};

この問題を解決するための私の試みは非常に不器用でした:

  • ブーストシグナルと同じシグネチャを持つクラスで新しいQtシグナルを作成しましたMainWindow
  • ブースト信号を処理するスロットで、新しいQt信号を出力し、ポインターを渡します
  • ポインタを受け取る新しいQtスロットを作成しました

新しいスロットで新しいウィンドウを開くと、すべてが機能します。しかし、これは非常に不器用だと思います。そのようなすべてのブースト信号をカスケードする必要がありますか、それともより良い方法がありますか?

4

2 に答える 2

1

紛らわしいのは、レンダリング要求からのシングルトンの呼び出しが、要求を生成しているスレッドから行われていることだと思います。シングルトンは一意のオブジェクトを返しますが、それが送信するシグナルは、要求しているスレッドのコンテキスト内にあります。このシグナルを実際に処理してメインスレッドにUIオブジェクトを作成するには、スレッドコンテキストをメインUIスレッドに明示的に切り替えるか許可するために何かを行う必要があります。

そして、あなたはあなたが説明するこのシーケンスでこれを暗黙のうちに行っています:

•Boostシグナルと同じシグネチャでMainWindowクラスに新しいQtシグナルを作成しました

•ブースト信号を処理するスロットで、新しいQt信号を出力し、ポインターを渡します

•ポインタを受け取る新しいQtスロットを作成しました

Qtシグナルとスロットは、クロススレッドシグナルを自動的にキューに入れます(注1)。したがって、ブースト信号を処理するスロットはまだ要求スレッドにあります。次に、Qt信号を発信します。Qtは、シグナルの受信者がメインスレッド(注2)にあることを検出しますが、送信者はリクエスタースレッドにあり、シグナルをキューに入れます。メインスレッドのメインQtイベントループがこのキューに入れられたイベントをイベントリストから削除すると、シグナルが自動的に再発行されますが、これでメインスレッドのコンテキストになり、UI操作が許可されます。

注1-この動作がconnect()呼び出しで明示的にオーバーライドされない限り、Qt::ConnectionTypeのドキュメントを参照してください。

注2-実際には、レシーバーのQObjectはメインスレッドによって所有されています。すべてのQObjectは、作成されたスレッドコンテキストのスレッドIDを保持します。

これがスレッドで何が起こっているのかを説明するのに役立つことを願っています。あなたの解決策は問題ありませんが、@ tmpearceが示唆したように、アダプターで物事をまとめると便利かもしれません。

于 2012-05-29T16:23:39.127 に答える
0

showObjectをスロットとして定義し、その本体に小さな数式を追加します。

if( QThread::currentThread() != thread() )
{
     bool ok = QMetaObject::invokeMethod(this, "showObject", Qt::QueuedConnection, Q_ARG(QObject *, object));

     if( ! ok )
         qDebug() << "Couldn't invoke method";

     return;
}

メソッド本体の残りの部分はそのままにしておきます。

于 2012-05-24T14:26:59.683 に答える