使用调度程序构建反应速度更快的应用程序

本文探讨了在Windows Presentation Foundation (WPF)中创建响应快速的用户界面的重要性及方法。文章介绍了WPF的线程模型,说明了如何利用DispatcherObject确保UI线程的正确访问,并演示了BackgroundWorker组件的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

果您在创建一个直观、自然甚至精美的界面上花费了数月时间,但结果是用户不得不在他们的组合办公桌上敲打着手指等待程序响应,这会让人觉得丢脸。由于长时间运行的进程导致应用程序的屏幕停滞不动,看到这样的情况是一件痛苦的事情。然而,创建响应迅速的应用程序需要进行认真的规划,这通常需要使长时间运行的进程在其他线程中工作,以便释放出 UI 线程,使其随时跟上用户的进度。

 

我第一次真正体验响应速度可追溯到 Visual C++® 与 MFC 以及我曾经编写的第一个网格。当时,我正在帮助编写一个药学应用程序,该程序必须能够将每种药物显示在复杂的处方中。问题是有 30,000 种药物,因此我们决定先在 UI 线程中填充第一个满屏药物(时间大约为 50 毫秒),给人一种反应迅速的印象,然后使用后台线程完成填充不可见的药物(时间大约为 10 秒)。项目运行良好,而且我学到了非常宝贵的经验,那就是用户感知可以比现实更重要。

在创建具有吸引力的用户界面方面,Windows® Presentation Foundation (WPF) 是一项出色的技术,但这并不意味着您就不需要考虑应用程序的响应性。不管相关的长时间运行进程的类型为何(不管是从数据库获取大量结果,进行异步 Web 服务调用,还是任何数量的其他潜在密集型操作),简单的事实就是,响应更快的应用程序是让用户更满意的长期保证。但是,开始在 WPF 应用程序中使用异步编程模型之前,了解 WPF 线程模型非常重要。在本文中,我不但将会向您介绍此线程模型,还会向您展示基于调度程序的对象的工作原理,以及解释如何使用 BackgroundWorker 以便创建具有吸引力和响应性的用户界面。

 

线程模型

所有 WPF 应用程序启动时都会加载两个重要的线程:一个用于呈现用户界面,另一个用于管理用户界面。呈现线程是一个在后台运行的隐藏线程,因此您通常面对的唯一线程就是 UI 线程。WPF 要求将其大多数对象与 UI 线程进行关联。这称之为线程关联,意味着要使用一个 WPF 对象,只能在创建它的线程上使用。在其他线程上使用它会导致引发运行时异常。注意,WPF 线程模型可与基于 Win32® 的 API 进行顺畅的交互。这意味着 WPF 可以承载或承载于任何基于 HWND 的 API(Windows Forms、Visual Basic®、MFC,甚至是 Win32)。

线程关联由 Dispatcher 类处理,该类即是用于 WPF 应用程序的、按优先级排列的消息循环。通常,WPF 项目有单个 Dispatcher 对象(因此有单个 UI 线程),所有用户界面工作均以其为通道。

与典型的消息循环不同,发送到 WPF 的每个工作项目都以特定的优先级通过 Dispatcher 进行发送。这就能够按优先级对项目排序,并延迟某种类型的工作,直到系统有时间来处理它们。(例如,有些工作项目可被延迟到系统或应用程序处于空闲状态时。) 支持项目优先顺序使 WPF 能够让某种类型的工作拥有更多的权限,因此在线程上拥有比其他工作更多的时间。

在本文的后面,我将会阐明,呈现引擎在更新用户界面方面比输入系统具备更高的优先级。这意味着不管用户是否正在使用鼠标、键盘或墨水打印系统,动画都将会继续更新用户界面。这可以使用户界面看起来响应更快。例如,让我们假定您正在编写一个音乐播放应用程序(类似于 Windows Media® Player)。不管用户是否正在使用界面,您最有可能希望显示有关音乐播放的信息(包括进度条和其他信息)。对用户来说,这可以使界面看起来对他们最感兴趣的事情(在此例中为听音乐)响应更快。

除了使用 Dispatcher 的消息循环将工作项目引导至用户界面线程之外,每个 WPF 对象也可感知对其负责的 Dispatcher(以及它由此所依赖的 UI 线程)。这意味着任何从第二个线程更新 WPF 对象的尝试均会失败。这就是 DispatcherObject 类的职责。

 

DispatcherObject

在 WPF 的类层次结构中,大部分都集中派生于 DispatcherObject 类(通过其他类)。如图 1 所示,您可以看到 DispatcherObject 虚拟类正好位于 Object 下方和大多数 WPF 类的层次结构之间。

 

 

 

 

 

 

 

 

 

 

 

派生

DispatcherObject 类有两个主要职责:提供对对象所关联的当前 Dispatcher 的访问权限,以及提供方法以检查 (CheckAccess) 和验证 (VerifyAccess) 某个线程是否有权访问对象(派生于 DispatcherObject)。CheckAccess 与 VerifyAccess 的区别在于 CheckAccess 返回一个布尔值,表示当前线程是否可以使用对象,而 VerifyAccess 则在线程无权访问对象的情况下引发异常。通过提供这些基本的功能,所有 WPF 对象都支持对是否可在特定线程(特别是 UI 线程)上使用它们加以确定。如果您正在编写您自己的 WPF 对象(诸如控件),那么您使用的所有方法都应在执行任何工作之前调用 VerifyAccess。这可确保您的对象仅在 UI 线程上使用

public class MyWpfObject : DispatcherObject
{
    public void DoSomething()      
    {
        VerifyAccess();

        // Do some work
    }

    public void DoSomethingElse()
    {
        if (CheckAccess())
        {
            // Something, only if called
            // on the right thread
        }
    }
}

 为此,在调用 Control、Window、Panel 之类的任何 DispatcherObject 派生对象时,应注意要处在 UI 线程上。
如果您从非 UI 线程调用 DispatcherObject,就会引发异常。
相反,如果您正在某个非 UI 线程上工作,就需要使用 Dispatcher 来更新 DispatcherObjects。
 续下 .....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值