在Qt GUI中快速更新许多小部件

时间:2014-02-09 19:43:35

标签: multithreading performance qt user-interface

我很难理解必须向屏幕显示大量数据的应用程序的最佳方法,该方法正在高速更新。我正在Qt for windows中编写这个应用程序。我不会详细介绍实际应用程序,但我在下面编写了一个示例应用程序来演示这个问题。

在这里,我有一个计算值的线程。在这种情况下,它只是一个计数器的值。在实际应用中它有很多价值。这是每毫秒更新一次。此速率是数据所需的计算速率,而不是GUI所需的更新速率。如上所述,这是在自己的线程中完成的。这个线程的想法是它只是计算数据,而不关心它的显示。

现在更新此示例中的数据显示我正在使用QLabel网格多次显示该值(模拟许多不同值的显示)。我从Qt文档中了解到,Widgets的更新必须在主GUI线程中完成。所以我在这里做的是我得到线程计算值,每次重新计算它时发出一个带有计算值的信号(1ms)。然后将其连接到主GUI,然后依次更新每个小部件以显示值。

这样做的结论是:

  1. GUI线程被数据线程的1ms更新淹没,因此更新显示变得非常慢。在我的机器上,有100个小部件更新我估计更新大约是4fps。
  2. GUI的所有其他方面(如移动,调整大小和按钮按下)都在努力争取处理器时间。
  3. 似乎用文本更新一个简单的QLabel似乎很慢。这是正常的吗?
  4. 我也为此添加了时序代码,GUI线程中的1ms更新在大约1.8ms的avergae上运行,最大增量时间为40-75ms。因此它的运行速度非常快,即使它落后了。但可能在幕后其他事件正在GUI线程事件队列中实际绘制屏幕更新,这些都是非常困难的。
  5. 我真的不明白是什么决定实际的屏幕更新率? Qt如何决定何时更新屏幕?
  6. 最重要的是,我不确定更新要显示的数据的正确方法是什么。很明显,屏幕不需要在1ms更新,即使它可能无论如何都不能快速刷新。另一方面,我不希望我的数据线程与屏幕更新率有关。有没有更好的方法将数据从数据线程获取到GUi而不会淹没GUI事件队列?

    非常感谢任何有关此问题的Qt方法的见解。

    这是在自己的线程中运行的数据生成器:

    class Generator : public QObject
    {
        Q_OBJECT
    public:
        explicit Generator(QObject *parent = 0);
    
    signals:
        void    dataAvailable(int val);
    public slots:
        void    run(bool run);
        void    update(void);
    private:
        QTimer  *timer;
        int     value;
    };
    
    void Generator::run(bool run)
    {
        if(run)
        {
            value = 0;
            timer = new QTimer;
            connect(timer, SIGNAL(timeout()), this, SLOT(update()));
            timer->start(1);
        } else
        {
            timer->stop();
            delete timer;
        }
    }
    
    void Generator::update()
    {
        value++;
        emit dataAvailable(value);
    }
    

    这是更新dislay的主要GUI类:

    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
    
    private:
        QLabel      *labels[ROW_MAX][COL_MAX];
        Generator   *dataGenerator;
    
    public slots:
        void dataAvailable(int val);
    
    signals:
        void runGenerator(bool run);
    };
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent)
    {
        QGridLayout *layout = new QGridLayout;
        for(int iCol=0; iCol<COL_MAX; iCol++)
        {
            for(int iRow=0; iRow<ROW_MAX; iRow++)
            {
                QLabel *label = new QLabel("Hello");
                labels[iRow][iCol] = label;
                layout->addWidget(labels[iRow][iCol], iRow, iCol);
            }
        }
        centralWidget->setLayout(layout);
    
        dataGenerator = new Generator;
        QThread *dgThread = new QThread;
        dataGenerator->moveToThread(dgThread);
        dgThread->start(QThread::HighestPriority);
    
        connect(this, SIGNAL(runGenerator(bool)), dataGenerator, SLOT(run(bool)));
        connect(dataGenerator, SIGNAL(dataAvailable(int)), this, SLOT(dataAvailable(int)));
    
        emit runGenerator(true);
    
    }
    
    void MainWindow::dataAvailable(int val)
    {
        for(int iCol=0; iCol< COL_MAX; iCol++)
        {
            for(int iRow=0; iRow<ROW_MAX; iRow++)
            {
                labels[iRow][iCol]->setText(QString::number(val));
            }
        }
    }
    

2 个答案:

答案 0 :(得分:2)

过去对我有用的一种方法是将访问器方法构建到worker对象中,然后让视图根据自己的更新周期“拉”数据。

要使用示例代码,请将此类方法添加到Generator

// TODO For a more complex class, you probably want one "get all the stats" 
// accessor rather than lots of little methods -- that way all the data is 
// synced up
int Generator::getCurrentValue()
{
    QMutexLocker(mutex); // (also add a QMutex member to the class)
    return value;
}

然后给主窗口一个自己的更新计时器,它不会非常严重地破坏系统:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    // ...
    // Replace dataAvailable code with this:
    updateTimer = new QTimer;
    connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateDisplayedValues());
    updateTimer->start(MY_MAIN_WINDOW_UPDATE_RATE); // some constant; try 200 ms?
    // ...
}

void MainWindow::updateDisplayedValues()
{
    int val = dataGenerator->getCurrentValue();

    // TODO You might this more efficient by checking whether you *need to*
    // repaint first here; generally Qt is pretty good about not wasting cycles 
    // on currently-hidden widgets anyway

    for(int iCol=0; iCol< COL_MAX; iCol++)
    {
        for(int iRow=0; iRow<ROW_MAX; iRow++)
        {
            labels[iRow][iCol]->setText(QString::number(val));
        }
    }
}

答案 1 :(得分:1)

如果您只是将计算放入在计时器上运行的生成器...它的事件循环与GUI在同一个线程上。

如果你将发生器移动到它自己的线程,然后设置一个定时器来检查发生器每秒最多30次,你会发现大多数问题都消失了。告诉它在视觉上更新显示的速度比人眼可以感知的速度快,或者显示器刷新率甚至可以做到,这通常是过度的。就普通视频速率而言,NTSC执行30 fps,PAL执行25 fps。对于数据的文本标签,我通常需要在一秒钟内进行采样/平均,并在视觉上每秒更新一次,以保持其可读性,而不仅仅是数字模糊。

当您开始使用线程时,您应该会看到您的计算机使用不同的内核来管理应用程序中的不同负载。如果你不使用多线程,你可以非常快速地进行任何苛刻的计算。

此外,当您连接到线程函数时,请务必使用QueuedConnection,而不是AutomaticConnection,因为大部分时间,如果您没有拼写,它会选择在线程之间走错了。

希望有所帮助。