WPF多線程UI更新

前言

  在WPF中,在使用多線程在後臺進行計算限制的異步操做的時候,若是在後臺線程中對UI進行了修改,則會出現一個錯誤:(調用線程沒法訪問此對象,由於另外一個線程擁有該對象。)這是很常見的一個錯誤,一不當心就會有這個現象。在WPF中,若是不是用多線程的話,例如單線程應用程序,就是說代碼一路過去都在GUI線程運行,能夠隨意更新任何東西,包括UI對象。可是使用多線程來更新UI就可能會出現以上所說問題,怎麼解決?本文章提供兩個方法:Dispatcher(大部分人使用),TaskScheduler(任務調度器)。程序員

 

問題再現多線程

  可能有的WPF新手不懂這是什麼狀況,先來個問題的再現,再使用本文章的兩個方法進行解決。dom

  爲了演示方便,我使用了最簡單的佈局,一個開始按鈕,三個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;
        }
    }

    運行一下,在點擊開始按鈕的時候,獲得了一個錯誤信息:this

  果真不出所料,Begin函數是在後臺線程執行的,tb這個TextBlock是前臺UI線程的對象,因此沒法在後臺線程改變UI線程擁有的對象,不少有點經驗的WPF程序員就會使用下面我要說的Dispatcher了!spa

 

問題解決線程

  方法一:Dispatchercode

    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下載。

相關文章
相關標籤/搜索