我使用同步块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));
}
}
答案 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();
}
}
另一种选择是将QEventLoop
与QTimer
一起使用:
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();
}