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. Invoke 或 BeginInvoke 来完成此操作。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 事件处理程序中不操作任何用户界面对象。而应该通过 ProgressChanged 和 RunWorkerCompleted 事件与用户界面进行通信。

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

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

 

 

 

 

 

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