WPF多線程UI更新——兩種方法


 

轉載:https://www.cnblogs.com/Jarvin/p/3756061.html

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM