多线程编程可使WPF应用程序执行后台工作,同时保持用户界面能够进行响应。
WPF支持单线程单元(Single-Thread Apartment)模型:
- 元素具有线程关联性 - 创建元素的线程拥有该元素,其他线程不能直接与该元素进行交互
- 具有关联性的WPF对象都继承自DispatcherObject类
- UI线程运行整个应用程序并拥有所有WPF对象
Dispatcher
调度程序(dispatcher) 拥有应用程序线程,并管理工作项队列。当程序运行时,调度程序接受新的工作请求,并且一次执行一个任务。
从技术上看,当在新线程中第一次实例化 DispatcherObject类的派生类时,会创建调度程序。如果创建线程相互独立,并用它们显示独立的窗口,最终将创建多个调度程序。不过,大部分都保持简单方式,坚持使用一个用户界面线程和一个调度程序。
DispatcherObject
DispatcherObject实例是绑定到调度程序线程的对象,提供了三个成员:
名称 | 说明 |
---|---|
Dispatcher | 返回管理该对象的调度程序 |
CheckAccess() | 如果代码在正确的线程上使用对象,返回true |
VerifyAccess() | 如果代码在正确的线程上使用对象,就什么都不做,否则抛出 InvalidOperationException 异常 |
WPF对象为保护自身会频繁调用 VerifyAccess() 方法,从而不可能在错误的线程中长时间使用一个对象。
- Dispatcher.BeginInvoke() 方法会将代码安排为调度程序的任务
- Dispatcher.Invoke() 方法将指定的代码封送到调度程序线程,并且会拖延线程直到调用程序执行你的代码
BackgroundWorker
有很多方法执行异步操作,比如创建 Thread 对象。可以创建几十个线程,但如果访问共享数据就需要使用锁定机制来避免潜在错误。另外频繁创建线程或大量创建线程会产生额外的,不必要的开销。
BackgroundWorker用于简化 Windows 窗体应用程序中与线程相关的问题。为单独线程中运行耗时的任务提供了简单的方法,它在后台使用调度程序,并使用基于事件的模型对封送问题进行抽象。可以使用两个方法创建实例:
- 在代码中创建 BackgroundWorker 对象,并使用代码关联事件处理程序
- 可在 XAML 中声明 BackgroundWorker 对象。这种优点是可使用特性关联事件处理程序
<Window.Resources>
<cm:BackgroundWorker x:Key="backgroundWorker"
WorkerReportsProgress="True" WorkerSupportsCancellation="True"
DoWork="backgroundWorker_DoWork"
ProgressChanged="backgroundWorker_ProgressChanged"
RunWorkerCompleted="backgroundWorker_RunWorkerCompleted"/>
</Window.Resources>
- 调用 RunWorkerAsynce() 方法时,可提供一个对象,然后会被传递到 DoWork 事件中。
- 当开始执行后,从CLR线程池提取一个自由线程,然后从这个线程引发DoWork事件.此时不能访问共享数据或用户界面
- 完成耗时工作就会引发 RunWorkerCompleted事件通知应用程序。这个事件在调度程序线程引发,此时可以访问共享数据和用户界面
跟踪进度
如果要实现 跟踪进度 ,首先DoWork事件处理代码需要调用 ReportProgress() 方法,并提供已经完成的百分比。每次调用 ReportProgress() 方法时,都会引发 ProgressChanged 事件,此事件是从用户界面线程引发的,所以不需要使用 Dispatcher.BeginInvoke()方法。
private void UpdateProgress()
{
backgroundWorker.ReportProgress(p);
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// 此事件在UI线程
progressBar.Value = e.ProgressPercentage;
}
支持取消
为请求取消,代码需要调用 CancelAsync() 方法。此时不会自动发生任何操作,相反执行任务的代码(DoWork方法)需要显示地检查取消请求,执行所有清除操作,然后返回。
private void cmdCancel_Click(object sender, RoutedEventArgs e)
{
// 手动触发取消
backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkerEventArgs e)
{
// 省略代码
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
return;
}
// 省略代码
e.Result = result;
}
当取消操作时,仍会引发 RunWorkerCompleted 事件,此时可检查任务是否已经被取消并进行相应处理。
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Cancelled)
{
MessageBox.Show("Search cancelled.");
}
else if(e.Error != null)
{
MessageBox.Show(e.Error.Message, "An Error Occurred");
}
else
{
// 正常结束耗时任务
}
progressBar.Value = 0;
}