2013/12/04

WPF 讓Thread透過SynchronizationContext更新UI資訊

專題遇到將資料即時更新到Label,但是資料並不是透過主執行緒去取得資料
像是RS232的資料傳輸,我是透過Thread去執行

原始程式碼如下:

using System;
using System.Threading;
using System.Windows;

namespace WpfApplicationTest
{
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private Thread th;


        private void MainButton_Update_Click(object sender, RoutedEventArgs e)
        {
            if (th == null)
            {
                th = new Thread(new ThreadStart(Update));
                th.Start();
            }
        }

        private void Update()
        {
            while (true)
            {
                int random_Number = new Random().Next();
                MainLabel_test.Content = random_Number;
                Thread.Sleep(1000);
            }
        }

    }
}



會出現這樣錯誤,是因為『不能跨執行緒去存取資源』
所以,我參考了這篇文章『[C#][WPF][Windows Form] 雙平台通用的跨執行緒存取UI』,寫得非常詳細,不過還是要自己寫看看。

他主要是提出使用『SynchronizationContext
SynchronizationContext:類別是一種基本類別,用來提供沒有同步處理的無限制執行緒內容。


修改過得程式碼如下:


using System;
using System.Threading;
using System.Windows;

namespace WpfApplicationTest
{
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //取得目前執行緒
            sync = SynchronizationContext.Current;
        }

        private Thread th;
        private SynchronizationContext sync;


        private void MainButton_Update_Click(object sender, RoutedEventArgs e)
        {
            if (th == null)
            {
                th = new Thread(new ThreadStart(Update));
                th.Start();
            }
        }

        private void Update()
        {
            while (true)
            {
                int random_Number = new Random().Next();
                sync.Send(new SendOrPostCallback(SyncUpdate), random_Number);
                Thread.Sleep(1000);
            }
        }

        private void SyncUpdate(object state)
        {
            MainLabel_test.Content = state;
        }

    }
}

先看到建構子的程式碼
        public MainWindow()
        {
            InitializeComponent();
            //取得目前執行緒
            sync = SynchronizationContext.Current;
        }

sync是宣告成SynchronizationContext變數,並且設定為當前視窗的主執行緒
使用SynchronizationContextSend方法讓同步訊息可以同步執行
        private void Update()
        {
            while (true)
            {
                int random_Number = new Random().Next();
                sync.Send(new SendOrPostCallback(SyncUpdate), random_Number);
                Thread.Sleep(1000);
            }
        }

並透過SendOrPostCallback委派,將值更新到MainLabel_test
        private void SyncUpdate(object state)
        {
            MainLabel_test.Content = state;
        }


執行結果:


我相信看完『MSDN 雜誌:平行計算 - SynchronizationContext 綜述』會更懂得箇中奧義

參考資料:
http://www.dotblogs.com.tw/optimist9266/archive/2011/06/08/27440.aspx
http://msdn.microsoft.com/zh-tw/library/System.Threading.SynchronizationContext(v=vs.80).aspx
http://msdn.microsoft.com/zh-tw/library/system.random(v=vs.110).aspx
http://msdn.microsoft.com/zh-tw/magazine/gg598924.aspx