QEventloop

Qt 如何处理密集型耗时的事情(频繁调用QApplication::processEvents)

转载 2017年03月03日 21:24:50

     有时候需要处理一些跟界面无关的但非常耗时的事情,这些事情跟界面在同一个线程中,由于时间太长,导致界面无法响应,处于“假死”状态。例如:在应用程序中保存文件到硬盘上,从开始保存直到文件保存完毕,程序不响应用户的任何操作,窗口也不会重新绘制,从而处于“无法响应”状态,这是一个非常糟糕的体验 。

     在这种情况下,有一种方法是使用多线程,即在子线程中处理文件保存,主线程负责界面相关。

     而如果不想使用多线程,最简单的办法就是在文件保存过程中频繁调用QApplication::processEvents()。该函数的作用是让程序处理那些还没有处理的事件,然后再把使用权返回给调用者。

代码如下:

复制代码
bool MyApp::writeFile(const QString &filename)
{
     QFile file(filename);
...
    QApplication::setOverrideCursor(Qt::WaitCursor);
     for(int r = 0; r != rowCount; ++r)
     {
          for(int c = 0; c != colCount; ++c)
          {
               out << table(r,c);   
               qApp.processEvents();
          }
     }
    QApplication::restoreOverrideCursor();
}
复制代码

     这样一来,程序就能响应了。

     但是,该方法有一个问题:可能正在保存文件的过程中,用户不小心又单击了保存,或不小心关闭了程序主窗口,这样会产生意想不到的后果。

     解决这个问题的最简单的办法是替换成:

qApp->processEvents(QEventLoop::ExcludeUserInputEvents);//它可以忽略用户的输入(鼠标和键盘事件)。

 

     进一步的,如果想显示一个带有进度条的对话框,随时显示当前的进度状态,可以使用QProgressDialog。

复制代码
bool MyApp::writeFile(const QString &filename)
{
     QFile file(filename);
...
    QApplication::setOverrideCursor(Qt::WaitCursor);
     QProgressDialog progress;
     progress.setWindowTitle(tableData->sNameCH);
     progress.setLabelText(QStringLiteral("数据保存中,请稍候..."));
     //progress.setCancelButton(0);//不显示“取消”按钮
     progress.setCancelButtonText("取消");
     progress.setRange(0,rowCount );
     progress.setModal(true);
     //此处没有调用show()来显示,是因为QProgressDialog会自动决定是否显示
     //如果时间过短,就不会显示。
     for(int r = 0; r != rowCount; ++r)
     {
          progress.setValue(row);
          //如果用户单击了“取消”,就取消保存文件,并删除该文件。
          if(progress.wasCanceled)
          {
               file.remov();
               return false;
          }
          for(int c = 0; c != colCount; ++c)
          {
               out << table(r,c);   
               qApp.processEvents();
          }
     }
    QApplication::restoreOverrideCursor();
}
复制代码

显示效果如下:

ScreenClip


关于Qt的事件循环以及QEventLoop的简单使用

1.一般我们的事件循环都是由exec()来开启的,例如下面的例子:

1 QCoreApplicaton::exec()
2 QApplication::exec()
3 QDialog::exec()
4 QThread::exec()
5 QDrag::exec()
6 QMenu::exec()

  这些都开启了事件循环,事件循环首先是一个无限“循环”,程序在exec()里面无限循环,能让跟在exec()后面的代码得不到运行机会,直至程序从exec()跳出。从exec()跳出时,事件循环即被终止。QEventLoop::quit()能够终止事件循环。

  事件循环实际上类似于一个事件队列,对列入的事件依次的进行处理,当时间做完而时间循环没有结束的时候,其实际上比较类似于一个不占用CPU事件的for(;;)循环。

  其本质实际上是以队列的方式来重新分配时间片。

2.事件循环是可以嵌套的,当在子事件循环中的时候,父事件循环中的事件实际上处于中断状态,当子循环跳出exec之后才可以执行父循环中的事件。当然,这不代表在执行子循环的时候,类似父循环中的界面响应会被中断,因为往往子循环中也会有父循环的大部分事件,执行QMessageBox::exec(),QEventLoop::exec()的时候,虽然这些exec()打断了main()中的QApplication::exec(),但是由于GUI界面的响应已经被包含到子循环中了,所以GUI界面依然能够得到响应。

3.如果某个子事件循环仍然有效,但其父循环被强制跳出,此时父循环不会立即执行跳出,而是等待子事件循环跳出后,父循环才会跳出

 

举几个例子吧,比如说如果想要将主线程等待100ms,总不能使用sleep吧,那样会导致GUI界面停止响应的,但是用事件循环就可以避免这一点:

1 QEventLoop loop;
2 QTimer::singleShot(100, &loop, SLOT(quit()));
3 loop.exec();

还有,比如说对于一个槽函数,触发之后会弹出一个dialog,但是像下面这样写的话,窗口会一闪而过的:

1 void ****::mySLot{
2     QDialog dlg;
3     dlg.show();
4 }

当然这里可以使用将dlg改成一个静态成员,通过增长期生存期的方法来解决这个问题,但是这里同样可以使用eventLoop来解决这个问题:

复制代码
1 void ****::mySLot{
2     QDialog dlg;
3     dlg.show();
4     QEventLoop loop;
5     connect(&dlg, SIGNAL(finished(int)), &loop, SLOT(quit()));
6     loop.exec(QEventLoop::ExcludeUserInputEvents);
7 }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值