使用调度程序构建反应速度更快的应用程序
Shawn Wildermuth
本文讨论:
| 本文使用了以下技术: .NET Framework 3.0, WIndows Presentation Foundation |
如果您在创建一个直观、自然甚至精美的界面上花费了数月时间,但结果是用户不得不在他们的组合办公桌上敲打着手指等待程序响应,这会让人觉得丢脸。由于长 时间运行的进程导致应用程序的屏幕停滞不动,看到这样的情况是一件痛苦的事情。然而,创建响应迅速的应用程序需要进行认真的规划,这通常需要使长时间运行 的进程在其他线程中工作,以便释放出 UI 线程,使其随时跟上用户的进度。
我第一次真正体验响应速度可追溯到 Visual C++® 与 MFC 以及我曾经编写的第一个网格。当时,我正在帮助编写一个药学应用程序,该程序必须能够将每种药物显示在复杂的处方中。问题是有 30,000 种药物,因此我们决定先在 UI 线程中填充第一个满屏药物(时间大约为 50 毫秒),给人一种反应迅速的印象,然后使用后台线程完成填充不可见的药物(时间大约为 10 秒)。项目运行良好,而且我学到了非常宝贵的经验,那就是用户感知可以比现实更重要。
在创建具有吸引力的用户界面方面,Windows® Presentation Foundation (WPF) 是一项出色的技术,但这并不意味着您就不需要考虑应用程序的响应性。不管相关的长时间运行进程的类型为何(不管是从数据库获取大量结果,进行异步 Web 服务调用,还是任何数量的其他潜在密集型操作),简单的事实就是,响应更快的应用程序是让用户更满意的长期保证。但是,开始在 WPF 应用程序中使用异步编程模型之前,了解 WPF 线程模型非常重要。在本文中,我不但将会向您介绍此线程模型,还会向您展示基于调度程序的对象的工作原理,以及解释如何使用 BackgroundWorker 以便创建具有吸引力和响应性的用户界面。
图 1 DispatcherObject 派生
DispatcherObject 类有两个主要职责:提供对对象所关联的当前 Dispatcher 的访问权限,以及提供方法以检查 (CheckAccess) 和验证 (VerifyAccess) 某个线程是否有权访问对象(派生于 DispatcherObject)。CheckAccess 与 VerifyAccess 的区别在于 CheckAccess 返回一个布尔值,表示当前线程是否可以使用对象,而 VerifyAccess 则在线程无权访问对象的情况下引发异常。通过提供这些基本的功能,所有 WPF 对象都支持对是否可在特定线程(特别是 UI 线程)上使用它们加以确定。如果您正在编写您自己的 WPF 对象(诸如控件),那么您使用的所有方法都应在执行任何工作之前调用 VerifyAccess。这可确保您的对象仅在 UI 线程上使用,如图 2 所示。
| 优先级 | 说明 |
|---|---|
| 非活动 | 工作项目已排队但未处理。 |
| SystemIdle | 仅当系统空闲时才将工作项目调度到 UI 线程。这是实际得到处理的项目的最低优先级。 |
| ApplicationIdle | 仅当应用程序本身空闲时才将工作项目调度到 UI 线程。 |
| ContextIdle | 仅在优先级更高的工作项目得到处理后才将工作项目调度到 UI 线程。 |
| 后台 | 在所有布局、呈现和输入项目都得到处理后才将工作项目调度到 UI 线程。 |
| 输入 | 以与用户输入相同的优先级将工作项目调度到 UI 线程。 |
| 已加载 | 在所有布局和呈现都完成后才将工作项目调度到 UI 线程。 |
| 呈现 | 以与呈现引擎相同的优先级将工作项目调度到 UI 线程。 |
| DataBind | 以与数据绑定相同的优先级将工作项目调度到 UI 线程。 |
| 正常 | 以正常优先级将工作项目调度到 UI 线程。这是调度大多数应用程序工作项目时的优先级。 |
| 发送 | 以最高优先级将工作项目调度到 UI 线程。 |
一 般来说,对于更新 UI 外观的工作项目(如我之前使用的示例),您应始终使用 DispatcherPriority.Normal 优先级。但也有时候应该使用不同的优先级。其中尤其令人感兴趣的是三个空闲优先级(ContextIdle、ApplicationIdle 和 SystemIdle)。通过这些优先级可以指定仅在工作负载很低的情况下执行的工作项目。
DispatcherTimer
WPF 线程资源
- Windows Presentation Foundation 虚拟实验室
- .NET Framework 开发中心的 WPF
- WPF 基础:线程模型,MSDN 库
- “Programming the Windows Presentation Foundation”(Windows Presentation Foundation 编程),作者 Chris Sells 和 Ian Griffiths (O'Reilly, 2005)
在 Microsoft® .NET Framework 中定期执行代码是开发中的一项常见任务,但是在 .NET 中使用计时器仍令人困惑。如果您在 .NET Framework 基类库 (BCL) 中查找 Timer 类,那么至少会找到 3 种 Timer 类:System.Threading.Timer、System.Timers.Timer 和 System.Windows.Forms.Timer。每种计时器均有所不同。Alex Calvo 在《MSDN 杂志》中的文章解释了何时使用这些 Timer 类中的每个类(请参见 msdn.microsoft.com/msdnmag/issues/04/02/TimersinNET)。
对于 WPF 应用程序来说,有一种使用 Dispatcher(即 DispatcherTimer 类)的新型计时器。与其他计时器类似,DispatcherTimer 类支持指定滴答之间的间隔,以及在计时器事件触发时要运行的代码。在图 8 中可以看到一种相当常见的 DispatcherTimer 使用方法。
wildermuthconsulting.com。