WPF线程模型学习
在视图使用多线程进行WPF编程的时候,新手如我一定会试过引发一个异常,大致的意思是“某个控件在线程a上创建,你不能在线程b上改动它”,例如使用ProgressBar去显示运算进度,当新建的运算线程完成,并在此线程上直接改动ProgressBar时,就会出现这种异常。
如何解决?
线程关联由 Dispatcher 类处理,该类即是用于 WPF 应用程序的、按优先级排列的消息循环。通常,WPF 项目有单个 Dispatcher 对象(因此有单个 UI 线程),所有用户界面工作均以其为通道。
1.DispatcherObject
DispatcherObject 类有两个主要职责:提供对对象所关联的当前 Dispatcher 的访问权限,以及提供方法以检查 (CheckAccess) 和验证 (VerifyAccess) 某个线程是否有权访问对象(派生于 DispatcherObject)。
Dispatcher 类提供了到 WPF 中消息泵的通道,还提供了一种机制来路由供 UI 线程处理的工作。这对满足线程关联要求是必要的,但是对通过 Dispatcher 路由的每个工作来说,UI 线程都被阻止,因此使 Dispatcher 完成的工作小而快非常重要。最好将用户界面的大块工作拆分为较小的离散块,以便 Dispatcher 执行。任何不需要在 UI 线程上完成的工作应移到其他线程上,以便在后台进行处理。
但是一定要注意,在新线程上更新界面,不能使用一下的手段
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 线程关联的 Dispatcher。我们可以使用Dispatcher. Invoke 或 BeginInvoke 来完成此操作。Invoke 是同步操作,而 BeginInvoke 是异步操作。该操作将按指定的 DispatcherPriority 添加到 Dispatcher 的事件队列中。
Invoke是同步操作;因此,直到回调返回之后才会将控制权返回给调用对象。Invoke有很多的重载用以传参。
对于开头说到的那个例子,可以使用以下的代码完成:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{
pb.Value = value;
}
private void testBtn_Click(object sender, RoutedEventArgs e)
{
ThreadStart start = delegate()
{
// ...
// This will work as its using the dispatcher
Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action<double>(SetStatus),
50);
};
new Thread(start).Start();
}
3、BackgroundWorker
http://msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker%28VS.80%29.aspx
貌似BackgroundWorker更简洁,但是它不能进行UI线程的操作。
BackgroundWorker 类允许您在单独的专用线程上运行操作。耗时的操作(如下载和数据库事务)在长时间运行时可能会导致用户界面 (UI) 似乎处于停止响应状态。如果您需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用 BackgroundWorker 类方便地解决问题。
若要设置后台操作,请为 DoWork 事件添加一个事件处理程序。在此事件处理程序中调用耗时的操作。若要启动该操作,请调用 RunWorkerAsync。若要收到进度更新通知,请对 ProgressChanged 事件进行处理。若要在操作完成时收到通知,请对 RunWorkerCompleted 事件进行处理。
您必须非常小心,确保在 DoWork 事件处理程序中不操作任何用户界面对象。而应该通过 ProgressChanged 和 RunWorkerCompleted 事件与用户界面进行通信。
BackgroundWorker 事件不跨 AppDomain 边界进行封送处理。请不要使用 BackgroundWorker 组件在多个 AppDomain 中执行多线程操作。
如果后台操作需要参数,请在调用 RunWorkerAsync 时给出参数。在 DoWork 事件处理程序内部,可以从 DoWorkEventArgs.Argument 属性中提取该参数。