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

@Adri 通常是正确的,但“另一个线程”必须再次成为 UI 线程。关键是允许 UI 线程的事件循环继续旋转。快速而肮脏的方法是放入QCoreApplication::processEvents()您的for()循环中。肮脏,因为正如文档所说,它应该被称为“偶尔”。即使没有 UI 事件,它也可能会产生一些开销,并且您正在搞乱 Qt 的性能优化,即何时以及多久旋转一次循环。在大块result.

更干净和正确的方法是创建一个私有插槽,它弹出一个结果元素(或块,以加快速度),添加到布局并增加索引。然后它会自动召回,直到结束resultsconnect()问题是使用强制连接类型来定义Qt::QueuedConnection,因此它会在已经排队的 UI 事件(如果有的话)之后被延迟。

而且因为你只在一个线程中运行,你不需要任何锁定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 回答
2

正如 Pavel Zdenek 所说,第一个技巧是只处理部分结果。您希望同时处理尽可能多的开销,以便(我们将在下一步中执行的操作)的开销很低,但您不想做任何会使系统看起来没有响应的事情。基于广泛的研究,Jakob Nielsen 说“0.1 秒大约是让用户感觉到系统在瞬间做出反应的极限”,因此粗略估计你应该将你的工作分成大约 0.05 秒的块(再留出 0.05 秒系统对用户的交互做出实际反应)。

第二个技巧是使用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 回答