• 热门专题

WPF多线程UI更新两种方法

作者:♂立地←  发布日期:2014-05-28 22:07:56
Tag标签:线程  方法  
  • 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下载。

     

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规