zoukankan      html  css  js  c++  java
  • 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下载。

  • 相关阅读:
    1.27
    1.25
    Representation Learning with Contrastive Predictive Coding
    Learning a Similarity Metric Discriminatively, with Application to Face Verification
    噪声对比估计(负样本采样)
    Certified Adversarial Robustness via Randomized Smoothing
    Certified Robustness to Adversarial Examples with Differential Privacy
    Dynamic Routing Between Capsules
    Defending Adversarial Attacks by Correcting logits
    Visualizing Data using t-SNE
  • 原文地址:https://www.cnblogs.com/DreamRecorder/p/10823398.html
Copyright © 2011-2022 走看看