1

多くのアイテムを行に表示するカスタム ウィジェットがあります。

void update(){ //this is a SLOT which is connected to a button click
    QVBoxLayout *layout = this->layout();
    if (layout == NULL){
        layout = new QVBoxLayout;
        this->setLayout(layout);
    } else {
        QLayout_clear(layout); //this is a function that I wrote that deletes all of the items from a layout
    }
    ArrayList *results = generateData(); //this generates the data that I load from
    for (int i = 0; i < results->count; i++){
        layout->addWidget(new subWidget(results->array[i]));
    }
}

問題は、約 900 のアイテムがあり、プロファイルによると、子オブジェクトをレイアウトに追加するだけで 50% の時間がかかる (構築に残りの 50% かかる) ことです。全体として、すべてのアイテムをロードするのに約 3 秒かかります。

ボタンをクリックしてさらにデータをロードすると、UI 全体が 3 秒間フリーズし、すべてが完了するとすべての項目が一緒に表示されます。作成中のアイテムを徐々にロードする方法はありますか?

4

2 に答える 2

2

最初のトリックは、Pavel Zdenek が言ったように、結果の一部のみを処理することです。(次のステップで実行することの) オーバーヘッドが低くなるようにできるだけ多くをまとめて処理する必要がありますが、システムが応答していないように見えるようなことはしたくありません。広範な調査に基づいて、Jakob Nielsen は、「システムが瞬時に反応しているとユーザーに感じさせるには 0.1 秒が限界です」と述べています。ユーザーのインタラクションに実際に反応するシステム)。

2 番目のトリックはQTimer、タイムアウト 0 で aを使用することです。QTimer のドキュメントには次のように書かれています。

特殊なケースとして、タイムアウトが 0 の QTimer は、ウィンドウ システムのイベント キュー内のすべてのイベントが処理されるとすぐにタイムアウトします。これは、きびきびしたユーザー インターフェイスを提供しながら、重い作業を行うために使用できます。

つまり、イベント キューに他に何か (マウス クリックなど) がない限り、タイムアウトが 0 のタイマーが次に実行されます。コードは次のとおりです。

void update() {
    i = 0; // warning, this is causes a bug, see below
    updateChunk();
}

void updateChunk() {
    const int CHUNK_THRESHOLD = /* the number of things you can do before the user notices that you're doing something */;
    for (; i < results->count() && i < CHUNK_THRESHOLD; i++) {
         // add widget
    }
    // If there's more work to do, put it in the event queue.
    if (i < results->count()) {
        // This isn't true recursion, because this method will return before
        // it is called again.
        QTimer::singleShot(0, this, SLOT(updateChunk()));
    }
}

最後に、落とし穴があるため、これを少しテストします。これで、ユーザーはループの「途中」でシステムと対話できます。たとえば、結果の処理中にユーザーが更新ボタンをクリックできます (上の例では、インデックスを 0 にリセットし、配列の最初の要素を再処理することを意味します)。したがって、より堅牢な解決策は、配列の代わりにリストを使用し、処理時にリストの先頭から各要素をポップすることです。次に、結果を追加するものはすべてリストに追加されます。

于 2012-12-27T02:47:09.987 に答える
2

@Adriは一般的に正しいです。ねじれは、「別のスレッド」が再びUIスレッドでなければならないということです。ポイントは、UI スレッドのイベント ループが回転し続けるようにすることです。手早く汚い方法はQCoreApplication::processEvents()、あなたのfor()サイクルに入れることです。ドキュメントが言うように、それは「時折」と呼ばれるべきであるため、汚れています。UI イベントがなくても、いくらかのオーバーヘッドが発生する可能性があり、いつ、どのくらいの頻度でループをスピンするかに関して、Qt のパフォーマンスの最適化を台無しにしています。result. _

よりクリーンで適切な方法は、プライベート スロットを作成することです。これは、1 つの結果要素 (またはチャンク、高速化) をポップし、レイアウトに追加し、インデックスをインクリメントします。その後、 の終わりまで自分自身をリコールしますresultsconnect()落とし穴は、強制接続タイプで定義するQt::QueuedConnectionことです。そのため、既にキューに入れられた UI イベント (存在する場合) の後に延期されます。

また、1 つのスレッドのみで実行するため、ロックする必要はありませんresults

OPのリクエストごとに例を追加:

@TomPanning ソリューションは正しいですが、必要のない QTimer の背後にある実際のソリューションを隠しています。タイミングは必要ありません。特定のパラメーター値に対する特定の非タイマー動作が必要なだけです。このソリューションは、QTimer レイヤーを除いて同じことを行います。一方、@TomPanning は、プレーンな ArrayList があまり適切なデータ ストレージではないことについて非常に良い点を示しています。

something.h

signals: void subWidgetAdded();
private slots: void addNextWidget();
ArrayList* m_results;
int m_indexPriv;

something.cpp

connect(this,SIGNAL(subWidgetAdded()),
        this,SLOT(addNextWidget(),
        Qt::QueuedConnection);

void addWidget() {
  // additional chunking logic here as you need
  layout->addWidget(new subWidget(results->array[m_indexPriv++]));
  if( m_indexPriv < results->count() ) {
    emit subWidgetAdded(); // NOT a recursion :-)
  }
}

void update() {
  // ...
  m_results = generateData();
  m_indexPriv = 0;
  addNextWidget(); // slots are normal instance methods, call for the first time
}
于 2012-12-26T20:51:11.027 に答える