现在的位置: 首页 > 综合 > 正文

WPF:谈谈各种多线程去修改或访问UI线程数据的方法

2013年04月04日 ⁄ 综合 ⁄ 共 2186字 ⁄ 字号 评论关闭

这属于老话题了,这篇文章就算做一个小汇总吧。

 

例子就比如要修改窗体的Title属性,单线程的话,直接写代码就可以了,比如在Window的Loaded事件中修改Title:

private void Window_Loaded_1(object sender, RoutedEventArgs e)

{

    Title = "Mgen";

}

 

直接用多线程修改的话:

System.Threading.ThreadPool.QueueUserWorkItem(_ => Title = "Mgen");

 

结果肯定是程序崩溃,抛出InvalidOperationException:The calling thread cannot access this object because a different thread owns it. 这是由于UI线程的数据不能直接被其他线程访问或者修改。

 

直接使用Task:

Task.Factory.StartNew(() => Title = "Mgen");

 

本质上也是非法操作,但是上述代码不会引发程序崩溃,同样Title属性也不会被修改。原因是Task的这个异常不会被立即抛出,此时Task的异常处于未觉察状态,这个未觉察状态的异常会在垃圾回收时终结器执行线程中被抛出(更多请参考这篇文章:.NET(C#) TPL:Task中未觉察异常和TaskScheduler.UnobservedTaskException事件

 

解决方案之一就是使用WPF的Dispatcher线程模型来修改,BeginInvoke会立即返回,Invoke会等执行完后再返回。

this.Dispatcher.BeginInvoke(new Action(() => Title = "Mgen"));

 

另一种方法就是使用SynchronizationContext,它的Post和Send方法类似BeginInvoke和Invoke,只不过SynchronizationContext抽象化了UI平台的线程模型,他是在System.Threading命名空间下,可以在Windows Forms和WPF中通用。

 

不过注意SynchronizationContext的Current属性只有在UI线程下才会为非null,因此需要通过SynchronizationContext.SetSynchronizationContext方法在多线程环境下设置SynchronizationContext.Current属性。那么上面的代码改用SynchronizationContext可以这样写:

//使用SynchronizationContext.SetSynchronizationContext方法在多线程环境下设置SynchronizationContext.Current属性

System.Threading.SynchronizationContext.SetSynchronizationContext(

    new System.Windows.Threading.DispatcherSynchronizationContext(App.Current.Dispatcher));

System.Threading.SynchronizationContext.Current.Send(_ => Title = "Mgen", null);

 

 

实际上上述代码在WPF下等效于调用Dispatcher.BeginInvoke方法,当然SynchronizationContext在不同UI框架下的执行都是不一样的。

 

最后TPL中的Task执行也支持SynchronizationContext,通过使用TaskScheduler.FromCurrentSynchronizationContext获取一个和当前SynchronizationContext相关的TaskScheduler,利用这个TaskScheduler,Task将通过当前SynchronizationContext来执行,这样内部调用Dispatcher的相关方法最终Task可以安全访问UI线程的数据。

 

代码:

Task.Factory.StartNew(() => Title = "Mgen", CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

 

最后在新的C# 5.0和.NET 4.5(目前还是beta)下,可以使用async/await来更好地简化异步变成,而且await后的代码会自动根据当前SynchronizationContext来执行。比如在另一个线程中执行一些操作后在修改主线程的Title属性。

 

如下示例代码:

//注意在方法中加async

private async void Window_Loaded_1(object sender, RoutedEventArgs e)

{

    //在另一个线程中睡眠500毫秒来模拟做工作

    await Task.Run(() => System.Threading.Thread.Sleep(500));

    //做完工作后修改主UI线程的数据

    Title = "Mgen";

}

抱歉!评论已关闭.