WPF线程模型学习

在视图使用多线程进行WPF编程的时候,新手如我一定会试过引发一个异常,大致的意思是“某个控件在线程a上创建,你不能在线程b上改动它”,例如使用ProgressBar去显示运算进度,当新建的运算线程完成,并在此线程上直接改动ProgressBar时,就会出现这种异常。

 

如何解决?

 

参考地址:http://msdn.microsoft.com/zh-cn/magazine/cc163328.aspx
 
所有 WPF 应用程序启动时都会加载两个重要的线程:一个用于呈现UI,另一个用于管理UI。呈现线程是一个在后台运行的隐藏线程,因此您通常面对的唯一线程就是管理UI的线程。WPF 要求将其大多数对象与该UI线程进行关联。这称之为线程关联,意味着要使用一个 WPF 对象,只能在创建它的线程上使用。在其他线程上使用它会导致引发运行时异常。

 

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

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

 

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

 1.DispatcherObject

 DispatcherObject 类有两个主要职责:提供对对象所关联的当前 Dispatcher 的访问权限,以及提供方法以检查 (CheckAccess) 和验证 (VerifyAccess) 某个线程是否有权访问对象(派生于 DispatcherObject)。

 

 

 2.调度程序

 Dispatcher 类提供了到 WPF 中消息泵的通道,还提供了一种机制来路由供 UI 线程处理的工作。这对满足线程关联要求是必要的,但是对通过 Dispatcher 路由的每个工作来说,UI 线程都被阻止,因此使 Dispatcher 完成的工作小而快非常重要。最好将用户界面的大块工作拆分为较小的离散块,以便 Dispatcher 执行。任何不需要在 UI 线程上完成的工作应移到其他线程上,以便在后台进行处理。

但是一定要注意,在新线程上更新界面,不能使用一下的手段

View Code
// 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 线程关联的 Dispatcher。我们可以使用Dispatcher. InvokeBeginInvoke 来完成此操作。Invoke 是同步操作,而 BeginInvoke 是异步操作。该操作将按指定的 DispatcherPriority 添加到 Dispatcher 的事件队列中。

Invoke是同步操作;因此,直到回调返回之后才会将控制权返回给调用对象。Invoke有很多的重载用以传参。

 

更多关于Invoke和BeginInvoke的消息可以参考:http://msdn.microsoft.com/zh-cn/library/ms615963.aspx

 对于开头说到的那个例子,可以使用以下的代码完成:

View Code
        void SetStatus(double value)
        {
            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 事件处理程序中不操作任何用户界面对象。而应该通过 ProgressChangedRunWorkerCompleted 事件与用户界面进行通信。

BackgroundWorker 事件不跨 AppDomain 边界进行封送处理。请不要使用 BackgroundWorker 组件在多个 AppDomain 中执行多线程操作。

如果后台操作需要参数,请在调用 RunWorkerAsync 时给出参数。在 DoWork 事件处理程序内部,可以从 DoWorkEventArgs.Argument 属性中提取该参数。

 

 

 

 

 

posted @ 2011-03-02 15:31  Hector  阅读(544)  评论(0编辑  收藏  举报