保持Qt GUI响应的几种方法

最开始使用Qt时就遇到过QT Gui失去响应的问题,我是用多线程的方式解决的,然而一般来讲,多线程是会下降程序的运行速度。html

以后,在使用QSqlQuery::execBatch()函数时,Qt Gui 又失去响应,虽然多线程能够解决,可是若是能用单线程很好解决的,最好不要用到多线程,由于多线程不只容易拖慢程序的速度,编程及维护的难度也更大,能用简单方法解决的,就不要用复杂的方法。sql

因而我再次搜索资料,指望在解决方案的选择与解决步骤上,可以获得一个全面而又细致的总结。数据库


Witold Wysota 的文章https://doc.qt.io/archives/qq/qq27-responsive-guis.html#performinglongoperations 总结的很是不错。编程

Jason Lee的翻译: http://blog.csdn.net/jasonblog/article/details/5568589服务器

因此本文是在此文基础上的部分翻译、理解与二次总结。总之,有删减,有补充,因此没写 '转' 字。网络


1、问题的来源与分析多线程

     首先,咱们要知道 “为何Qt Gui 会中止响应?”。简明扼要的说就是:长时间的密集处理或等待阻塞了Qt的事件循环,应用程序不能响应来自窗口系统的事件请求(《C++ Gui Qt4》 P135中有描述)。   那么多长算长呢?一秒钟算长,两秒钟太长。框架

     其次,“ 何种情形下会发生该问题? ”。可分为两种情形:异步

     第一,长时间按顺序执行的密集运算,所有计算结束后才能继续执行,如快速傅立叶变换。函数

     第二,“ 触发 ”了某项操做,该操做完成后才能进行“ 下一步 ”, 因此这里描述的是异步操做,如保存文件操做,服务器等待链接、网络下载等。详细见附注(1).

     私觉得两种情形并没有明显的概念上的区分,本质是同样的,但两种情形有不一样的处理方法,特别是第二种情形, 在Qt框架下 ,用Qt的信号和槽机制每每能够解决阻塞问题,如QTcpServer::newConnection信号通知链接的到来,QIODevice::bytesWritten()与 QIODevice::readyRead()通知文件的读写,它们都是以非阻塞的形式实现相关功能的利器。 而第一种情形,不只全部的事件循环中止了,信号和槽也暂时被忽视。咱们将针对以上两种情形寻找解决方案。

     最后,咱们考虑是否能够把这个形成 Qt Gui 中止响应的罪魁祸首大卸八块,即把他拆分红一个个小块,若是能够拆分,那么每块之间是依赖仍是独立,若是独立那问题好办,放在不一样的位置独立运做,不然,咱们只能同步的执行,而最差的结果是——根本没法拆分!!

     总之,考虑以上信息差别,执行不一样的解决方案。


2、解决方案

Manual event processing(人工执行事件)

     保持事件循环有一种最基本的方法——让程序去处理 悬挂事件好了 ,处理完了再回来继续个人后续运算,要作到这一点,就要在个人运算代码中间加上处理事件的代码,这句代码就是 QCoreApplication::processEvents();,只要该句代码可以周期性的被执行,就能保持Qt Gui的响应。

//代码来源于上述连接所指向文章

for (int i = 3; i <= sqrt(x) && isPrime; i += 2) {
        label->setText(tr("Checking %1...").arg(i));
        if (x % i == 0)
            isPrime = false;
        QCoreApplication::processEvents();
        if (!pushButton->isChecked()) {
            label->setText(tr("Aborted"));
            return;
        } 

    }

部分翻译(略)——可查看 Jason Lee的翻译

     该方案除了 具备Witold Wysota文中 所提到缺点以外,《C++ Gui Qt4》P135中还提到,用户可能会在应用程序还在执行某种操做时,或者关闭了主窗口,或者经过界面再次触发相同操做,这样就会产生不可预料的后果,如 一个保存文件对话框,用户单击save按钮后,程序开始磁盘文件的写入操做,该操做还未完成时,用户再次单击了关闭按钮,或者再次单击save按钮。书中给出的解决办法是将 qApp->processEvents()替换为qApp-> processEvents(QEventLoop::ExcludeUserInputEvents),以告诉Qt忽略鼠标事件和键盘事件。

 

Using a Worker Thread(使用任务线程)

     翻译(略)—— Jason Lee的翻译

除了 Witold Wysota 文中所说的从新实现QThread类以外,还可使用QObject::moveToThread(QThread *thread)函数,将

进行复杂运算的对象移入子线程中运行,前提是子线程不可以有父对象,不然没法移入子线程。示例以下:

QThread *thread = new QThread(this); 
MyComputation *computation = new  MyComputation();//负责密集运算的对象 
computation->moveToThread(thread); 
connect(thread, SIGNAL(started()), computation ,SLOT( compute()) ); //compute()为computation的运算函数 
thread->start();

需注意的是,将 computation 对象移入子线程后, 依旧不可直接调用  computation 对象 compute()函,应该调用线程对象的start()函数,发出started()信号触发 computation 对象的运算操做,不然依旧会阻塞主线程。

 

Waiting in a Local Event Loop(在本地事件循环中等待)

翻译(略)—— Jason Lee的翻译

注解:如文章开头所说,“等待异步事件完成”,也就是说这种方法是针对异步事件而设计的,异步事件执行过程当中会不断发送信号,咱们根据该信号决定程序接下来的行为,包括人工执行事件。而  Manual event processing 适用于顺序执行的操做 。

 

Solving a Problem Step by Step(分步骤解决问题)

翻译(略)

如前文所说,若是一个复杂操做能够拆分为独立的子操做,那么拆分应该是最好的解决办法。至于如何拆分,能够经过阅读《重构》这本书来学习。

 

Parallel Programming
翻译(略)


3、总结

     前面我提到过,我是用queryBatch()函数致使了Qt Gui没法响应的,最后我选择了 Using a Worker Thread这种方法。queryBatch()是一个操做数据库的批处理函数,很是便利,但它是顺序执行的,我没法用异步方式来处理它,函数内部是不可见的,也没法人工执行事件或者拆分,最后只能使用子线程来执行它了,这就是为便利所付出的代价吧。不一样状况有不一样的解决方案,认清本身的问题很重要。


4、附注

(1) “ 触发 ”了某项操做,该操做完成后才能进行“ 下一步 ”,但实际上对于“触发”这个行为自己而言,它的职责已经完成了,而

“下一步”指的是某个功能执行中的“下一步”,固然也多是编程语境下的“ 下一步 ”,即下一条语句, 因此这里描述的是异步操做,如保存文件操做,服务器等待链接、网络下载等。

     举个栗子, 建立了QTcpServer对象并调用listen() 函数监听链接,这时若是调用QTcpServer::waitForNewConnection(...)函数,就会阻塞程序的运行,直到链接到来函数才能返回,进而执行下一句。这里,listen()函数“ 触发 ”了监听行为, “ 下一步 ”与网络链接相关的动做要等链接到来后才能执行,而 QTcpServer::waitForNewConnection(...)则做为咱们是否执行下一步的判断标准,只不过这里用的是阻塞的方式;对于异步操做,也能够用非阻塞的方式解决,Qt的信号和槽机制就能很好的解决,如咱们能够接收newConnection()信号判断链接是否到来,而没必要将程序阻塞在那。

相关文章
相关标签/搜索