<!-- [if gte mso 9]><xml> <w:WordDocument> <w:View>Normal</w:View> <w:Zoom>0</w:Zoom> <w:PunctuationKerning/> <w:DrawingGridVerticalSpacing>7.8 磅</w:DrawingGridVerticalSpacing> <w:DisplayHorizontalDrawingGridEvery>0</w:DisplayHorizontalDrawingGridEvery> <w:DisplayVerticalDrawingGridEvery>2</w:DisplayVerticalDrawingGridEvery> <w:ValidateAgainstSchemas/> <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> <w:IgnoreMixedContent>false</w:IgnoreMixedContent> <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> <w:Compatibility> <w:SpaceForUL/> <w:BalanceSingleByteDoubleByteWidth/> <w:DoNotLeaveBackslashAlone/> <w:ULTrailSpace/> <w:DoNotExpandShiftReturn/> <w:AdjustLineHeightInTable/> <w:BreakWrappedTables/> <w:SnapToGridInCell/> <w:WrapTextWithPunct/> <w:UseAsianBreakRules/> <w:DontGrowAutofit/> <w:UseFELayout/> </w:Compatibility> <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> </w:WordDocument> </xml><![endif]--><!-- [if gte mso 9]><xml> <w:LatentStyles DefLockedState="false" LatentStyleCount="156"> </w:LatentStyles> </xml><![endif]--><!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} h2 {mso-margin-top-alt:auto; margin-right:0cm; mso-margin-bottom-alt:auto; margin-left:0cm; mso-pagination:widow-orphan; mso-outline-level:2; font-size:18.0pt; font-family:宋体; mso-bidi-font-family:宋体;} a:link, span.MsoHyperlink {color:blue; text-decoration:underline; text-underline:single;} a:visited, span.MsoHyperlinkFollowed {color:purple; text-decoration:underline; text-underline:single;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.Section1 {page:Section1;} --><!-- [if gte mso 10]> <mce:style><!-- /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.0pt; font-family:"Times New Roman"; mso-fareast-font-family:"Times New Roman"; mso-ansi-language:#0400; mso-fareast-language:#0400; mso-bidi-language:#0400;} --> <!-- [endif]-->

Keeping the GUI Responsive


在 QtCentre 里的人们经常提到一个反复出现的问题:长操作期间 GUI


长操作

第一件事是列出这个问题域并且列出可以采取的解决方案。前面涉及的这个问题可能以两种形式出现。第一种情况是程序在执行计算密集型任务,也就是需要经过一系列操作才能得到最后的结果。比如快速傅里叶变换。

另一种情况是程序必须在已经触发的活动(比如从网络上下载东西)结束后才能继续算法的下一步。这种情况,就本身而言,通过使用 Qt 是比较容易避免的,因为大多数的异步操作在 Qt

在计算过程中(无论以何种方式使用信号和槽)所有的事件处理都会被暂停。因此, GUI

本文旨在保持程序功能,防止终端用户被一个不响应的 GUI

我们可以通过两种方式来获取计算型任务的结果——通过在主线程(单一线程方案)或者在单独的线程(多线程解决方案)进行计算。后者较多地为人所知并且在 JAVA

该问题域可以看做是两种情形组成的。我们不一定能够将问题细化成更小的步骤、循环或者子问题(通常它不应该是一个整体)。如果问题可以细化,它们也不一定互相依赖。如果它们之间是独立的,我们可以任意处理它们。否则,我们必须同步我们的任务。最坏的情况下,我们只能一次处理一些并且只有上一步结束了才能开始下一步。充分考虑这些因素,我们可以选择不同的解决方案。


人工的事件处理

最基本的解决方案就是显示地要求 Qt 在计算过程的某个点处理悬挂事件。为此,需要周期性地调用 QCoreApplication::processEvents()

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; } }

这种方法有明显的缺点。比如,假设你想并发地执行两个循环——调用其中一个会暂定另一个,直到该调用结束(所以不能在不同任务之间不能分配计算量)。它也造成了程序对事件处理的延迟。更重要的是代码难以阅读和分析,因此这种方案只适合短而简单的、在单一线程中处理的问题,比如闪屏或短期操作监控。


使用任务线程

另一种不同的解决方案就是在单独的线程中执行长操作从而避免阻塞主事件循环。假如任务是由第三方库通过阻塞方式执行,这是特别有效的。在这种情况下,它是不可能去打扰 GUI

一种可以完全控制独立线程的方式是使用 QThread 。可以继承它并且重写它的 run() 函数,或者调用 QThread:exec() 来启动线程的事件循环,或者两者共用:继承然后必要的时候在 run() 函数里调用 exec() 。可以通过信号和槽来连接主线程——只要记住 QueuedConnection

由于有很多多线程相关材料,所以这里不列举代码示例了,而把注意力集中在其它地方。


在本地事件循环中等待

要介绍的下一种处理等待异步任务结束的方案已经完结。这里,将要讨论如何在一个网络操作结束前阻塞程序流,并不阻塞事件处理。从本质上说,我们可以做的就是:

task.start(); while (!task.isFinished()) QCoreApplication::processEvents();

这叫做忙等待——频繁判断一个条件是否满足。大多数情况下这是一个坏主意,它占用了全部 CPU

幸运的是, Qt 有一个类来帮助我们处理这项任务: QEventLoop 是程序和模态对话框在它们 exec() 调用里使用的相同的类。每个该类的实例都连接到主事件分发机制,并且一旦它的 exec() 函数激活,它就开始处理事件直到使用 quit()

我们利用这种机制结合信号和槽使得异步操作转化为同步操作——我们可以开启一个本地事件循环然后通过特定对象的特定信号告知它结束退出:

QNetworkAccessManager manager; QEventLoop q; QTimer tT; tT.setSingleShot(true); connect(&tT, SIGNAL(timeout()), &q, SLOT(quit())); connect(&manager, SIGNAL(finished(QNetworkReply*)), &q, SLOT(quit())); QNetworkReply *reply = manager.get(QNetworkRequest( QUrl("http://www.qtcentre.org"))); tT.start(5000); // 5s timeout q.exec(); if(tT.isActive()){ // download complete tT.stop(); } else { // timeout }

我们使用一个网络访问管理类获取远程 URL 。由于它工作在异步方式,我们创建了一个本地事件循环来等待下载者的结束信号。此外,我们实例化一个定时器在五秒后来结束事件循环以免发生错误。通过连接合适的信号,提交请求和开始定时器,我们进入了新建的事件循环。对 exec()

在这里需要注意另外两件事。一个通过 QxtSignalWaiter 类实现类似的解决方案是 libqxt 项目的一部分。另外一件事是,对于一些操作而言, Qt 提供了一系列的“等待”方法(比如 QIODevice::waitForBytesWritten() )或多或少地做些和上面代码片段一样的事,但并没有运行一个事件循环。然而,“等待”方案会冻结 GUI