Dispatcher 类提供了到 WPF 中消息泵的通道,还提供了一种机制来路由供 UI 线程处理的工作。这对满足线程关联要求是必要的,但是对通过 Dispatcher 路由的每个工作来说,UI 线程都被阻止,因此使 Dispatcher 完成的工作小而快非常重要。最好将用户界面的大块工作拆分为较小的离散块,以便 Dispatcher 执行。任何不需要在 UI 线程上完成的工作应移到其他线程上,以便在后台进行处理。
通常,您将会使用 Dispatcher 类将工作项目发送到 UI 线程进行处理。例如,如果您想要使用 Thread 类在单独的线程上进行一些工作,那么可以创建一个 ThreadStart 委托,在新的线程上进行一些工作,代码如下:
// 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();
此代码执行失败,原因是当前没有在 UI 线程上调用对 statusText 控件(一种 TextBlock)的 Text 属性的设置。当该代码尝试设置 TextBlock 上的 Text 时,TextBlock 类会在内部调用其 VerifyAccess 方法以确保该调用来自 UI 线程。当它确定调用是来自不同的线程时,则会引发异常。那么您如何使用 Dispatcher 在 UI 线程上进行调用呢?
Dispatcher 类提供了在 UI 线程上直接调用代码的权限。
// 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();
代码展示了使用 Dispatcher 的 Invoke 方法来调用名叫 SetStatus 的方法,从而更改 TextBlock 的 Text 属性。
该 Invoke 调用包含三条信息:要执行的项目的优先级、说明要执行何种工作的委托,以及任何传递给第二个参数中所述委托的参数。通过调用 Invoke,它将要在 UI 线程上调用的委托排入队列。使用 Invoke 方法可确保在 UI 线程上执行工作之前保持阻止。
作为一种异步使用 Dispatcher 的替代方法,您可以使用 Dispatcher 的 BeginInvoke 方法为 UI 线程异步排队工作项目。调用 BeginInvoke 方法会返回一个 DispatcherOperation 类的实例,其中包含有关执行工作项目的信息,包括工作项目的当前状态和执行的结果(如果工作项目已完成)。BeginInvoke 方法和 DispatcherOperation 类的使用如下所示。
// 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();
与典型的消息泵实现不同,Dispatcher 是基于优先级的工作项目队列。这就能够实现更好的响应性,因为重要性更高的工作能够在重要性较低的工作之前执行。优先顺序的本质可通过 DispatchPriority 枚举中指定的优先级加以例证(如下 所示)。
优先级 | 说明 |
---|---|
非活动 | 工作项目已排队但未处理。 |
SystemIdle | 仅当系统空闲时才将工作项目调度到 UI 线程。这是实际得到处理的项目的最低优先级。 |
ApplicationIdle | 仅当应用程序本身空闲时才将工作项目调度到 UI 线程。 |
ContextIdle | 仅在优先级更高的工作项目得到处理后才将工作项目调度到 UI 线程。 |
后台 | 在所有布局、呈现和输入项目都得到处理后才将工作项目调度到 UI 线程。 |
输入 | 以与用户输入相同的优先级将工作项目调度到 UI 线程。 |
已加载 | 在所有布局和呈现都完成后才将工作项目调度到 UI 线程。 |
呈现 | 以与呈现引擎相同的优先级将工作项目调度到 UI 线程。 |
DataBind | 以与数据绑定相同的优先级将工作项目调度到 UI 线程。 |
正常 | 以正常优先级将工作项目调度到 UI 线程。这是调度大多数应用程序工作项目时的优先级。 |
发送 | 以最高优先级将工作项目调度到 UI 线程 |
一般来说,对于更新 UI 外观的工作项目(如我之前使用的示例),您应始终使用 DispatcherPriority.Normal 优先级。但也有时候应该使用不同的优先级。其中尤其令人感兴趣的是三个空闲优先级(ContextIdle、ApplicationIdle 和 SystemIdle)。通过这些优先级可以指定仅在工作负载很低的情况下执行的工作项目。
BackgroundWorker
现在您对 Dispatcher 的工作原理已有所了解,那么如果得知在大多数情况下都不会使用它,您可能会感到惊讶。在 Windows Forms 2.0 中,Microsoft 引入了一个用于非 UI 线程处理的类来为用户界面开发人员简化开发模型。此类称为 BackgroundWorker。
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";
} }
代码显示了 BackgroundWorker 类的典型用法。
BackgroundWorker 组件与 WPF 的配合非常好,因为在后台它使用了 AsyncOperationManager 类,该类随之又使用 SynchronizationContext 类来处理同步。在 Windows Forms 中,AsyncOperationManager 递交从 SynchronizationContext 类派生的 WindowsFormsSynchronizationContext 类。同样,在 ASP.NET 中,它与 SynchronizationContext 的不同派生(称为 AspNetSynchronizationContext)配合使用。这些 SynchronizationContext 派生的类知道如何处理方法调用的跨线程同步。
在 WPF 中,可用 DispatcherSynchronizationContext 类来扩展此模型。通过使用 BackgroundWorker,可自动应用 Dispatcher 来调用跨线程方法调用。好消息是,由于您可能已经熟悉了这个常见的模式,因此可以继续在新的 WPF 项目中使用 BackgroundWorker。