WPF多线程UI更新——两种方法
2017-04-06 16:31
211 查看
WPF多线程UI更新——两种方法
前言
在WPF中,在使用多线程在后台进行计算限制的异步操作的时候,如果在后台线程中对UI进行了修改,则会出现一个错误:(调用线程无法访问此对象,因为另一个线程拥有该对象。)这是很常见的一个错误,一不小心就会有这个现象。在WPF中,如果不是用多线程的话,例如单线程应用程序,就是说代码一路过去都在GUI线程运行,可以随意更新任何东西,包括UI对象。但是使用多线程来更新UI就可能会出现以上所说问题,怎么解决?本文章提供两个方法:Dispatcher(大部分人使用),TaskScheduler(任务调度器)。问题再现
可能有的WPF新手不懂这是什么情况,先来个问题的再现,再使用本文章的两个方法进行解决。
为了演示方便,我使用了最简单的布局,一个开始按钮,三个TextBlock。按一下开始按钮,开一个后台线程随机得到一个数字,并且更新第一个TextBlock。再开另外一个后台线程得到另外一个数字,更新第二个TextBlock。第三个TextBlock处理同理。
XAML代码:
<Window x:Class="UpdateUIDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="130" Width="363"> <Canvas> <TextBlock Width="40" Canvas.Left="38" Canvas.Top="27" Height="29" x:Name="first" Background="Black" Foreground="White"></TextBlock> <TextBlock Width="40" Canvas.Left="128" Canvas.Top="27" Height="29" x:Name="second" Background="Black" Foreground="White"></TextBlock> <TextBlock Width="40" Canvas.Left="211" Canvas.Top="27" Height="29" x:Name="Three" Background="Black" Foreground="White"></TextBlock> <Button Height="21" Width="50" Canvas.Left="271" Canvas.Top="58" Content="开始" Click="Button_Click"></Button> </Canvas> </Window>
后台代码:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { Task.Factory.StartNew(Work); } private void Work() { Task task = new Task((tb) => Begin(this.first), this.first); Task task2 = new Task((tb) => Begin(this.second), this.first); Task task3 = new Task((tb) => Begin(this.Three), this.first); task.Start(); task.Wait(); task2.Start(); task2.Wait(); task3.Start(); } private void Begin(TextBlock tb) { int i=100000000; while (i>0) { i--; } Random random = new Random(); String Num = random.Next(0, 100).ToString(); tb.Text = Num; } }
运行一下,在点击开始按钮的时候,得到了一个错误信息:
果然不出所料,Begin函数是在后台线程执行的,tb这个TextBlock是前台UI线程的对象,所以无法在后台线程改变UI线程拥有的对象,很多有点经验的WPF程序员就会使用下面我要说的Dispatcher了!
问题解决
方法一: Dispatcher
1.把UI更新的代码放到一个函数中:
private void UpdateTb(TextBlock tb, string text) { tb.Text = text; }
2.使用Dispatcher,大家看修改后的Begin函数(红色内容):
private void Begin(TextBlock tb) { int i=100000000; while (i>0) { i--; } Random random = new Random(); String Num = random.Next(0, 100).ToString(); Action<TextBlock, String> updateAction = new Action<TextBlock, string>(UpdateTb); tb.Dispatcher.BeginInvoke(updateAction,tb,Num); }
再运行一次程序,可以看到能正常显示了,并且不会出现假死现象。
方法二:任务调度器( TaskScheduler )
有很多任务调度器,在CLR Var C#中就提出了线程池任务调度器,I/O任务调度器,任务限时调度器等,调度器的职责就是负责任务的调度,调节任务执行。同步上下文任务调度器就是该方法二所使用的调度器,其作用是将所有任务都调度给应用程序的GUI线程。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private readonly TaskScheduler _syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(SchedulerWork);
}
private void SchedulerWork()
{
Task.Factory.StartNew(Begin, this.first).Wait();
Task.Factory.StartNew(Begin, this.second).Wait();
Task.Factory.StartNew(Begin, this.Three).Wait();
}
private void Begin(object obj)
{
TextBlock tb = obj as TextBlock;
int i = 100000000;
while (i>0)
{
i--;
}
Random random = new Random();
String Num = random.Next(0,100).ToString();
Task.Factory.StartNew(() => UpdateTb(tb, Num),
new CancellationTokenSource().Token, TaskCreationOptions.None, _syncContextTaskScheduler).Wait();
}
private void UpdateTb(TextBlock tb, string text) { tb.Text = text; }
}
结果展示:
总结
任务调度器还有很多种,按照自己喜欢的方法来实现后台多线程更新UI。还有任务调度器也可以应用到Winform中。下面提供示例Demo下载。
DEMO:
http://download.csdn.net/detail/u012546338/9805390
相关文章推荐
- WPF多线程UI更新——两种方法
- 两种方法得到一站式示例代码库的及时更新
- 【2012.12.18更新】WPF支持GIF的各种方法
- wpf 分别用前台和后台 两种方法 绘制矩形 填充
- .NET一个线程更新另一个线程的UI(两种实现方法及若干简化)
- win8系统的两种界面更新方法图文详细介绍
- 一个简单登陆框的变化+更新了如何连接sql数据库,获得数据集,比较两种方法(数据集和读数据)
- Android 更新UI的两种方法——handler和runOnUiThread()
- Mysql两种情况下更新字段中部分数据的方法
- 方法更新Linq中两种更新操作
- 线程里更新UI的两种方法:Thread+Handler和Runnable
- Word中样式更新的两种方法!
- .NET一个线程更新另一个线程的UI(两种实现方法及若干简化)
- 遍历WPF窗体元件的两种方法
- Android 更新UI的两种方法——handler和runOnUiThread()
- 两种方法创建WPF目录树TreeView和GridView数据绑定
- Android中两种不同的方法实现实时更新时间,是显示时间和系统时间同步
- 在WPF中使用Emgu加载Image<,>图像的两种方法
- Android 更新UI的两种方法——handler和runOnUiThread()
- LeetCode Gas Station 两个特性,两种方法完美解答-更新证明方法