Multithreading Qt-GUI中许多小部件的快速更新

Multithreading Qt-GUI中许多小部件的快速更新,multithreading,performance,qt,user-interface,Multithreading,Performance,Qt,User Interface,对于必须在屏幕上显示大量数据的应用程序,我很难理解最佳方法,因为屏幕正在以高速更新。我正在用Qt for windows编写这个应用程序。我不会详细介绍实际应用程序,但我已经在下面编写了一个示例应用程序来演示这个问题 在这里,我有一个线程,它正在计算值。在这种情况下,它是一个值,它只是一个计数器。在实际应用中,它有很多价值。这是每毫秒更新一次。此速率是所需的数据计算速率,而不是GUI所需的更新速率。如前所述,这是在它自己的线程中完成的。这个线程的想法是,它只是关于数据的计算,而不关心数据的显示

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

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

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

这样做的结论是:

  • GUI线程被来自数据线程的1ms更新淹没,因此显示更新变得非常缓慢。在我的机器上,有100个小部件更新,我估计更新速度约为4fps
  • GUI的所有其他方面,如移动、调整大小和按下按钮,都在努力争取处理器时间
  • 似乎仅仅用文本更新一个简单的QLabel相当慢。这正常吗
  • 我还为此添加了计时代码,GUI线程中的1ms更新在avergae上运行约1.8ms,最大增量时间为40-75ms。所以它跑得相当快,尽管它落后了。但据推测,在幕后,GUI线程事件队列上的其他事件实际上是在屏幕上绘制更新,而这些事件确实很困难
  • 我真的不明白是什么决定了实际的屏幕更新率?Qt如何决定何时更新此处的屏幕
  • 最重要的是,我不确定更新要显示的数据的正确方法是什么。很明显,屏幕不需要以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));
            }
        }
    }
    
    class主窗口:公共QMainWindow
    {
    Q_对象
    公众:
    显式主窗口(QWidget*parent=0);
    私人:
    QLabel*标签[行最大值][列最大值];
    生成器*数据生成器;
    公众时段:
    无效数据可用(int val);
    信号:
    无效发电机(bool运行);
    };
    主窗口::主窗口(QWidget*父窗口):
    QMainWindow(父窗口)
    {
    QGridLayout*layout=新的QGridLayout;
    用于(int iCol=0;iColsetLayout(布局);
    数据生成器=新的生成器;
    QThread*dgThread=新的QThread;
    dataGenerator->moveToThread(dgThread);
    dgThread->start(QThread::HighestPriority);
    连接(此,信号(runGenerator(bool)),数据生成器,插槽(run(bool)));
    连接(dataGenerator,信号(dataAvailable(int)),此,插槽(dataAvailable(int));
    生成程序(真);
    }
    void主窗口::数据可用(int val)
    {
    对于(int iCol=0;iCol
    过去对我有效的一种方法是将访问器方法构建到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));
            }
        }
    }
    
    MainWindow::MainWindow(QWidget*父项):
    QMainWindow(父窗口)
    {
    // ...
    //将dataAvailable代码替换为以下代码:
    updateTimer=新的QTimer;
    连接(updateTimer,SIGNAL(timeout()),此,插槽(updateDisplayValues());
    UpdateTime->start(我的主窗口更新速率);//某个常数;尝试200毫秒?
    // ...
    }
    void main window::updateDisplayedValues()
    {
    int val=dataGenerator->getCurrentValue();
    //要做到这一点,您可以通过检查是否需要来提高效率*
    //首先在这里重新绘制;一般来说,Qt在不浪费周期方面非常好
    //在当前隐藏的小部件上
    对于(int iCol=0;iCol
    如果您只是将计算放入一个在计时器上运行的生成器中……它的事件循环与GUI在同一线程上

    如果将生成器移动到它自己的线程,然后设置一个计时器以每秒最多30次检查生成器,您应该会看到大多数问题都消失了。告诉它以比人眼所能感觉到的或监视器刷新率更快的速度直观地更新显示通常是过分的。NTSC每秒30次,PAL每秒2次5 fps,就普通视频速率而言。对于数据的文本标签,我通常需要在一秒钟内进行采样/平均,并在视觉上每秒更新一次,以保持可读性,而不仅仅是模糊的数字

    当您开始使用线程时,您应该看到您的计算机使用不同的内核来管理应用程序中的不同负载