WPF Threads : Build More Responsive Apps With The Dispatcher

This article discusses:
  • Threading in WPF
  • Using the Dispatcher
  • Non-UI thread handling
  • Using timers
This article uses the following technologies:
.NET Framework 3.0, WIndows Presentation Foundation
It would be a shame if you put months of your life into creating an intuitive, natural, and even beautiful interface only to have your users tapping their fingers on their collective desks waiting for it to respond. Watching your application screech to a halt because of a long-running process is just painful. However, creating applications that are responsive takes some careful planning, which usually entails having long-running processes work in other threads so that the UI thread is free to keep up with the user.
My first real experience with responsiveness goes way back to Visual C++ ® and MFC and the first grid I ever wrote. I was helping write a pharmacy application that had to be able to show every drug in a complex formulary. The problem was that there were 30,000 drugs, so we decided to give the appearance of responsiveness by filling the first screenful of drugs in the UI thread (in about 50 milliseconds), then using a background thread to finish filling the non-visible drugs (in about 10 seconds). The project went well and I learned the valuable lesson that user perception can be more important than reality.
Windows ® Presentation Foundation (WPF) is a great technology for creating compelling user interfaces, but that doesn't mean you don't have to take the responsiveness of your application into account. The simple fact is, no matter what type of long-running processes are involved—whether getting large results from a database, making asynchronous Web service calls, or any number of other potentially intensive operations—making your application more responsive is guaranteed to make your users much happier in the long run. But before you can start using an asynchronous programming model in your WPF application, it is important that you understand the WPF threading model. In this article I will not only introduce you to that threading model, but I'll also show you how the Dispatcher-based objects work and explain how to use the BackgroundWorker so that you can create compelling and responsive user interfaces.

The Threading Model
All WPF applications start out with two important threads, one for rendering and one for managing the user interface. The rendering thread is a hidden thread that runs in the background, so the only thread that you ordinarily deal with is the UI thread. WPF requires that most of its objects be tied to the UI thread. This is known as thread affinity, meaning you can only use a WPF object on the thread on which it was created. Using it on other threads will cause a runtime exception to be thrown. Note that the WPF threading model interoperates well with Win32 ®-based APIs. This means that WPF can host or be hosted by any HWND-based API (Windows Forms, Visual Basic ®, MFC, or even Win32).
The thread affinity is handled by the Dispatcher class, a prioritized message loop for WPF applications. Typically your WPF projects have a single Dispatcher object (and therefore a single UI thread) that all user interface work is channeled through.
Unlike typical message loops, each work item that is sent to WPF is sent through the Dispatcher with a specific priority. This allows for both ordering of items by priority and deferring certain types of work until the system has time to handle them. (For example, some work items can be deferred until the system or application is idle.) Supporting item prioritization allows WPF to allow certain types of work to have more access, and therefore more time on a thread, than other work.
Later in this article I will demonstrate that the rendering engine is allowed to update the user interface at a higher priority than the input system. This means that animations will continue to update the user interface no matter what the user is doing with the mouse, keyboard, or ink system. This can make the user interface appear more responsive. For example, let's assume you were writing a music-playing application (like Windows Media ® Player). You would most likely want the information about the music playing (including the progress bar and other information) to show up whether the user was using the interface or not. To the user, this can make the interface appear more responsive to what they are most interested in (listening to music, in this case).
In addition to using the Dispatcher's message loop to channel items of work through the user interface thread, every WPF object is aware of the Dispatcher that is responsible for it (and therefore, the UI thread that it lives on). This means that any attempts to update WPF objects from secondary threads will fail. This is the responsibility of the DispatcherObject class.

DispatcherObject
In the hierarchy of classes in WPF, most derive centrally from the DispatcherObject class (through other classes). As shown in Figure 1, you can see that the DispatcherObject virtual class is sandwiched just below Object in the hierarchy of most WPF classes.
Figure 1  Dispatcher­Object Derivation 
The DispatcherObject class has two chief duties: to provide access to the current Dispatcher that an object is tied to and provide methods to check (CheckAccess) and verify (VerifyAccess) that a thread has access to the object (derived from DispatcherObject). The difference between CheckAccess and VerifyAccess is that CheckAccess returns a Boolean value that represents whether the current thread can use the object and VerifyAccess throws an exception if the thread does not have access to the object. By providing this basic functionality, all the WPF objects support being able to determine whether they can be used on a particular thread—specifically, the UI thread. If you are writing your own WPF objects, such as controls, all methods you use should call VerifyAccess before they perform any work. This guarantees that your objects are only used on the UI thread, as shown in Figure 2.
public class MyWpfObject : DispatcherObject
{
    public void DoSomething()       
    {
        VerifyAccess();

        // Do some work
    }

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

With this in mind, be careful to be on the UI thread when calling any DispatcherObject-derived object such as Control, Window, Panel, and so on. If you make a call to a DispatcherObject from a non-UI thread, it will throw an exception. Instead, if you are working on a non-UI thread, you'll need to use the Dispatcher to update DispatcherObjects.

Using the Dispatcher
The Dispatcher class provides a gateway to the message pump in WPF and provides a mechanism to route work for processing by the UI thread. This is necessary to meet the thread affinity demands, but since the UI thread is blocked for each piece of work routed through the Dispatcher, it is important to keep the work that the Dispatcher does small and quick. It is better to break apart larger pieces of work for the user interface into small discrete blocks for the Dispatcher to execute. Any work that doesn't need to be done on the UI thread should instead be moved off onto other threads for processing in the background.
Typically you will use the Dispatcher class to send worker items to the UI thread for processing. For example, if you want to do some work on a separate thread using the Thread class, you could create a ThreadStart delegate with some work to do on a new thread as shown in Figure 3.
// The Work to perform on another thread
ThreadStart start = delegate()
{
    // ...

    // This will throw an exception 
    // (it's on the wrong thread)
    statusText.Text = "From Other Thread";
};

// Create the thread and kick it started!
new Thread(start).Start();

This code fails because setting the Text property of the statusText control (a TextBlock) is not being called on the UI thread. When the code attempts to set the Text on the TextBlock, the TextBlock class internally calls its VerifyAccess method to ensure that the call is coming from the UI thread. When it determines the call is from a different thread, it throws an exception. So how can you use the Dispatcher to make the call on the UI thread?
The Dispatcher class provides access to invoke code on the UI thread directly. Figure 4 shows the use of the Dispatcher's Invoke method to call a method called SetStatus to change the TextBlock's Text property for you.
// The Work to perform on another thread
ThreadStart start = delegate()
{
  // ...

  // Sets the Text on a TextBlock Control.
  // This will work as its using the dispatcher
  Dispatcher.Invoke(DispatcherPriority.Normal, 
                    new Action<string>(SetStatus), 
                    "From Other Thread");
};
// Create the thread and kick it started!
new Thread(start).Start();
The Invoke call takes three pieces of information: the priority of the item to be executed, a delegate that describes what work to perform, and any parameters to pass into the delegate described in the second parameter. By calling Invoke, it queues up the delegate to be called on the UI thread. Using the Invoke method ensures that you are going to block until the work is performed on the UI thread.
As an alternative to using the Dispatcher synchronously, you can use the BeginInvoke method of the Dispatcher to asynchronously queue up a work item for the UI thread. Calling the BeginInvoke method returns an instance of the DispatcherOperation class that contains information about the execution of the work item, including the current status of the work item and the result of the execution (once the work item has completed). The use of the BeginInvoke method and the DispatcherOperation class is shown in Figure 5.
// The Work to perform on another thread
ThreadStart start = delegate()
{
    // ...

    // This will work as its using the dispatcher
    DispatcherOperation op = Dispatcher.BeginInvoke(
        DispatcherPriority.Normal, 
        new Action<string>(SetStatus), 
        "From Other Thread (Async)");
    
    DispatcherOperationStatus status = op.Status;
    while (status != DispatcherOperationStatus.Completed)
    {
        status = op.Wait(TimeSpan.FromMilliseconds(1000));
        if (status == DispatcherOperationStatus.Aborted)
        {
            // Alert Someone
        }
    }
};

// Create the thread and kick it started!
new Thread(start).Start();

Unlike a typical message pump implementation, the Dispatcher is a priority-based queue of work items. This allows for better responsiveness because the more important pieces of work are executed before less important pieces of work. The nature of the prioritization is exemplified by the priorities that are specified in the DispatchPriority enumeration (as shown in Figure 6).

PriorityDescription
InactiveWork items are queued but not processed.
SystemIdleWork items are only dispatched to the UI thread when the system is idle. This is the lowest priority of items that are actually processed.
ApplicationIdleWork items are only dispatched to the UI thread when the application itself is idle.
ContextIdleWork items are only dispatched to the UI thread after higher-priority work items are processed.
BackgroundWork items are dispatched after all layout, rendering, and input items are processed.
InputWork items are dispatched to the UI thread at the same priority as user input.
LoadedWork items are dispatched to the UI thread after all layout and rendering are complete.
RenderWork items are dispatched to the UI thread at the same priority as the rendering engine.
DataBindWork items are dispatched to the UI thread at the same priority as data binding.
NormalWork items are dispatched to the UI thread with normal priority. This is the priority at which most application work items should be dispatched.
SendWork items are dispatched to the UI thread with the highest priority.
Generally you should always use DispatcherPriority.Normal for the priority of work items that update the appearance of the UI (like the example I used earlier). But there are times when you should use different priorities. Of particular interest are the three idle priorities (ContextIdle, ApplicationIdle, and SystemIdle). These priorities allow you to specify work items that are only executed under very low workloads.

BackgroundWorker
Now that you have a sense of how the Dispatcher works, you might be surprised to know that you will not find use for it in most cases. In Windows Forms 2.0, Microsoft introduced a class for non-UI thread handling to simplify the development model for user interface developers. This class is called the BackgroundWorker. Figure 7 shows typical usage of the BackgroundWorker class.
BackgroundWorker _backgroundWorker = new BackgroundWorker();

...

// Set up the Background Worker Events
_backgroundWorker.DoWork += _backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += 
    _backgroundWorker_RunWorkerCompleted;

// Run the Background Worker
_backgroundWorker.RunWorkerAsync(5000);

...

// Worker Method
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Do something
}

// Completed Method
void _backgroundWorker_RunWorkerCompleted(
    object sender, 
    RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        statusText.Text = "Cancelled";
    }
    else if (e.Error != null) 
    {
        statusText.Text = "Exception Thrown";
    }
    else 
    {
        statusText.Text = "Completed";
    }
}

The BackgroundWorker component works well with WPF because underneath the covers it uses the AsyncOperationManager class, which in turn uses the SynchronizationContext class to deal with synchronization. In Windows Forms, the AsyncOperationManager hands off a WindowsFormsSynchronizationContext class that derives from the SynchronizationContext class. Likewise, in ASP.NET it works with a different derivation of SynchronizationContext called AspNetSynchronizationContext. These SynchronizationContext-derived classes know how to handle the cross-thread synchronization of method invocation.
In WPF, this model is extended with a DispatcherSynchronizationContext class. By using BackgroundWorker, the Dispatcher is being employed automatically to invoke cross-thread method calls. The good news is that since you are probably already familiar with this common pattern, you can continue using BackgroundWorker in your new WPF projects.

DispatcherTimer
Periodic execution of code is a common task in development within the Microsoft ® .NET Framework, but using timers in .NET has been, at best, confusing. If you look for a Timer class in the .NET Framework Base Class Library (BCL) you will find no fewer than three Timer classes: System.Threading.Timer, System.Timers.Timer, and System.Windows.Forms.Timer. Each of these timers is different. Alex Calvo's article in MSDN Magazine explains when to use each of these Timer classes (see msdn.microsoft.com/msdnmag/issues/04/02/TimersinNET).
For WPF applications, there is a new type of timer that utilizes the Dispatcher—the DispatcherTimer class. Like other timers, the DispatcherTimer class supports specifying an interval between ticks as well as the code to run when the timer's event fires. You can see a fairly pedestrian use of the DispatcherTimer in Figure 8.
// Create a Timer with a Normal Priority
_timer = new DispatcherTimer();

// Set the Interval to 2 seconds
_timer.Interval = TimeSpan.FromMilliseconds(2000); 

// Set the callback to just show the time ticking away
// NOTE: We are using a control so this has to run on 
// the UI thread
_timer.Tick += new EventHandler(delegate(object s, EventArgs a) 
{ 
    statusText.Text = string.Format(
        "Timer Ticked:  {0}ms", Environment.TickCount); 
});

// Start the timer
_timer.Start();

Because the DispatcherTimer class is tied to Dispatcher you can also specify the DispatcherPriority as well as which Dispatcher to use. The DispatcherTimer class uses the Normal priority as the current Dispatcher by default, but you can override these values:
_timer = new DispatcherTimer(
    DispatcherPriority.SystemIdle, form1.Dispatcher);
It is well worth all the effort involved in planning your worker processes to enable more responsive applications. Doing some initial research can make that planning more succesful. I suggest exploring some of the sites mentioned in the "WPF Threading References" sidebar before you begin—along with this article, they should give you a good foundation for developing responsive apps.
源码下载地址: https://pan.quark.cn/s/a4b39357ea24 谷歌公司设计了一款无费用且具备开源特性的网络浏览器,名为Chrome,因其卓越的速度、稳定性和安全性而广受赞誉。该浏览器运用了前沿的Web渲染引擎Blink以及JavaScript引擎V8,旨在保障网页载入与脚本运行的卓越效能。为应对无网络环境下的Chrome安装需求,特别准备了离线安装包。此压缩文件内含32位与64位两种规格的Chrome浏览器离线安装方案,具体文件名分别为"chromedev_x64-v68.0.3423.2.exe"与"chromedev_x86-v68.0.3423.2.exe"。在文件命名中,"x64"标识64位版本,适用于64位操作系统平台,而"x86"则对应32位版本,适配32位操作系统。文件名中的"v68.0.3423.2"代表Chrome的一个特定版本号,各版本可能涵盖安全补丁、性能改进或新增功能。与32位Chrome相比,64位版本具备如下长处:能够处理更多内存容量,从而提升多任务作业能力;针对现代硬件的优化使其运行更为迅猛;64位版本更具备高级别的安全防护,能更周全地抵御恶意软件的侵袭。尽管如此,32位版本对于仍在使用32位操作系统的用户,或是在系统资源需求不高的场景下,依然适用。在部署Chrome浏览器时,用户需依据其个人计算机的操作系统平台,挑选匹配的版本进行安装。通过双击相应的.exe文件,安装流程将自动启动,一般包含接受使用许可、确定安装路径及构建桌面快捷方式等环节。若在安装阶段遭遇难题,可参照提示信息或联系技术支援获取协助,同时该压缩文件发布者亦表明欢迎用户以留言形式反映问题。Chrome浏览器的主要特质涵盖:直观的用户界面设计...
内容概要:本文围绕直驱式永磁同步电机(PMSM)矢量控制系统的建模与仿真展开研究,基于Simulink平台构建了完整的控制系统仿真模型,涵盖了电机本体数学建模、三相/两相坐标变换(Clarke/Park变换)、磁场定向控制(FOC)、电流环与速度环双闭环PID控制策略、空间矢量脉宽调制(SVPWM)技术以及转速调节器设计等核心技术环节。通过仿真实验验证了该控制策略在动态响应速度、稳态运行精度及抗负载扰动能力方面的优良性能,充分体现了矢量控制在实现电机高性能调速中的优势,为永磁同步电机在工业驱动、新能源汽车和高端装备制造等领域的实际应用提供了可靠的理论依据与技术支撑。; 适合人群:具备电机学、电力电子技术和自动控制原理基础知识的电气工程、自动化、机电一体化等相关专业的研究生、高校教师、科研人员,以及从事电机驱动系统、新能源汽车电驱、工业自动化设备研发的工程技术人员。; 使用场景及目标:①深入理解永磁同步电机矢量控制的基本原理与实现机制;②掌握在Simulink中搭建高精度电机控制系统仿真模型的方法与技巧;③为电机控制算法的设计、优化与参数整定提供高效的仿真验证平台;④服务于高校课程设计、毕业课题研究、科研项目前期验证及企业产品开发中的控制策略测试。; 阅读建议:建议结合经典电机控制教材进行对照学习,重点关注各功能模块间的信号流向、反馈机制与参数耦合关系,动手复现并调试仿真模型,通过改变PI参数、负载条件和给定转速等方式观察系统响应,从而深入掌握控制策略的内在逻辑与性能优化方法。
代码下载地址: https://pan.quark.cn/s/a4b39357ea24 Java学习路线(鱼皮)是一个全面且循序渐进的Java开发技能培养方案,该路线从基础入门直至高级应用,致力于协助学习者高效地掌握Java编程的全部核心内容。此学习路线的独特之处在于其新颖性、系统性、实践性、开放性以及社区回馈与持续迭代更新。其核心构成涵盖了预备阶段、Java入门知识、Java进阶技能、Java高级技术、Java框架应用以及Java项目实践等多个学习模块,每个模块均整合了相应的知识点、学习策略与资源指引。在预备阶段,学习者需配置在线编程环境、选择笔记工具、熟悉Markdown文档编写等基本技能,为编程学习奠定基础。在Java入门阶段,学习者应重点掌握Java编程的基础理论、开发环境配置、IDEA集成开发环境的使用、项目创建与执行调试、界面设置及插件配置等关键技能。在Java入门阶段,学习者还须深入理解Java基础语法、数据结构类型、程序流程控制、数组操作、面向对象编程、方法重载机制、封装原则、继承特性、多态表现、抽象类的概念、接口定义、枚举类型、常用类库、字符串处理、日期时间管理、集合框架、泛型编程、注解应用、异常处理机制、多线程技术、IO流操作、反射机制等核心知识点。在Java进阶阶段,学习者需要重点学习Java 8的更新特性、Stream API的应用、Lambda表达式的使用、新的日期时间处理API以及接口默认方法的实现。在Java高级阶段,学习者需要掌握Java框架的应用、Spring Boot框架的搭建、Spring Cloud微服务架构的实施等高级技术。在Java项目阶段,学习者需要学习Java项目开发的全过程操作,包括项目架构设计、项目编码实现、项...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值