是什么导致GUI和非GUI程序中的同步块功能“ sleep”的不同结果

时间:2018-06-26 13:57:23

标签: c++ qt qt5

我使用同步块QThread::sleep()进行计时,按时间依次显示第一名。

预期的运行过程是:同步阻塞当前线程2秒,然后运行以下代码以更改显示的数字,然后同步阻塞另外2秒,依此类推……,这种想法在非GUI程序中效果很好。

但是在GUI模式下,标签仅显示9,这是最后显示的数字。

是什么导致GUI和非GUI程序中的同步阻止功能sleep的不同结果?

#include <windows.h>
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QThread>
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

//slot of start timing button
void Widget::on_pushButton_2_clicked()
{
    for(int i=0;i<10;i++){
        QThread.sleep(2);
        ui->label->setText(QString::number(i));
    }
}

1 个答案:

答案 0 :(得分:2)

GUI需要连续验证诸如鼠标,键盘等事件,并在满足某些条件的情况下执行操作,即事件循环。对于sleep(),这是一个阻止任务,它不允许事件循环运行,从而导致GUI冻结(如果要验证,请尝试更改窗口的大小),因此在GUI内部线程,则应避免使用此类函数,而阻塞任务则必须将其转变为异步或在另一个线程中执行。

但是sleep()的任务可以用QTimer代替,而不会阻塞GUI:

*。h

#ifndef WIDGET_H
#define WIDGET_H

#include <QTimer>
#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_pushButton_2_clicked();
    void onTimeout();
private:
    Ui::Widget *ui;
    int counter;
    QTimer timer;
};

#endif // WIDGET_H

*。cpp

#include "widget.h"
#include "ui_widget.h"

#include <QLabel>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    counter = 0;
    connect(&timer, &QTimer::timeout, this, &Widget::onTimeout);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_2_clicked()
{
    timer.start(2000);
}

void Widget::onTimeout()
{
    ui->label->setText(QString::number(counter));
    counter++;
    if(counter > 10){
        counter = 0;
        timer.stop();
    }
}

另一种选择是将QEventLoopQTimer一起使用:

void Widget::on_pushButton_2_clicked()
{
    for(int i=0;i<10;i++){
        QEventLoop loop;
        QTimer::singleShot(2000, &loop, &QEventLoop::quit);
        loop.exec();
        ui->label->setText(QString::number(i));
    }
}

更新:

是什么导致GUI和非GUI程序中的同步阻塞功能休眠的不同结果?

如果您使用Qt创建非GUI应用程序,尽管效果可能不太明显,您也会遇到问题。

就我所说的GUI而言,有一个处理事件的事件循环,其中包括重绘事件,我的意思是当您在QLabel中设置新文本时,它不会自动绘制,而是Qt决定合适的时机。这就是为什么使用QThread::sleep()时没有时间更新绘画的原因。

很明显,在非GUI应用程序中,eventloop不会验证许多事件,因为它绘制的事件看不到效果,实际上,在仅打印数字的脚本中,eventloop不会验证任何事件。

要注意此问题,请使用以下示例:

#include <QCoreApplication>
#include <QThread>
#include <QTimer>

#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, [](){
       qDebug()<< "hello world";
    });

    timer.start(1000);

    qDebug()<< "start blocking";
    QThread::sleep(10);
    qDebug()<< "end blocking";

    return a.exec();
}

我们将看到sleep()结束之前什么也不会打印,也就是说,阻塞了允许QTimer完成其工作的事件循环。

回答您的评论:

  • 但仍然让我感到困惑的是,为什么在执行完睡眠功能后,停止阻塞当前线程后,以下代码无法正常运行

  • ui->label->setText(QString::number(i));此语句,紧接在睡眠功能之后

诸如绘画之类的异步任务比同步任务具有更低的优先级,也就是说,首先Qt将执行for循环,然后才执行异步任务,因此在for中,存储QLabel文本的变量是更新后,即0、1,...,9,然后将任务移交给eventloop,以便它仅绘制最后一个值,即9。

注意:

您可以使用QXXXApplication::processEvents()强制在同步执行中更新evenloop,但这通常被认为是不好的做法,我只是展示它,以便您知道它,但避免使用它:

for(int i=0;i<10;i++){
    QThread.sleep(2);
    ui->label->setText(QString::number(i));
    QApplication::processEvents();
}