天天看點

溫故而知新:WinForm/Silverlight多線程程式設計中如何更新UI控件的值

單線程的winfom程式中,設定一個控件的值是很easy的事情,直接 this.TextBox1.value = "Hello World!";就搞定了,但是如果在一個新線程中這麼做,比如:

private void btnSet_Click(object sender, EventArgs e)

{    

    Thread t = new Thread(new ParameterizedThreadStart(SetTextBoxValue));

    //當然也可以用匿名委托寫成Thread t = new Thread(SetTextBoxValue);

    t.Start("Hello World");

}

void SetTextBoxValue(object obj) 

{

    this.textBox1.Text = obj.ToString();

 運作時,會報出一個無情的錯誤:

線程間操作無效: 從不是建立控件“textBox1”的線程通路它。

究其原因,winform中的UI控件不是線程安全的,如果可以随意在任何線程中改變其值,你建立一個線程,我建立一個線程,大家都來搶着更改"TextBox1"的值,沒有任何秩序的話,天下大亂...

解決辦法:

1.掩耳盜鈴法(Control.CheckForIllegalCrossThreadCalls = false;)--僅Winform有效

using System;

using System.Threading;

using System.Windows.Forms;

namespace ThreadTest

    public partial class Form1 : Form

    {        

        public Form1()

        {

            InitializeComponent();

            Control.CheckForIllegalCrossThreadCalls = false;//這一行是關鍵      

        }

        private void btnSet_Click(object sender, EventArgs e)

        {           

            Thread t = new Thread(new ParameterizedThreadStart(SetTextBoxValue));

            t.Start("Hello World");

        void SetTextBoxValue(object obj) 

            this.textBox1.Text = obj.ToString();

        }        

    }

設定Control.CheckForIllegalCrossThreadCalls為false,相當于不檢測線程之間的沖突,允許各路線程随便亂搞,當然最終TextBox1的值到底是啥難以預料,隻有天知道,不過這也是最省力的辦法

2.利用委托調用--最常見的辦法(僅WinForm有效)

    {

        delegate void D(object obj);

            InitializeComponent();            

            if (textBox1.InvokeRequired)

            {

                D d = new D(DelegateSetValue);

                textBox1.Invoke(d,obj);

            }

            else 

                this.textBox1.Text = obj.ToString();

        void DelegateSetValue(object obj) 

3.利用SynchronizationContext上下文 -- 最神秘的方法(Winform/Silverlight能用)

之是以說它神秘,是因為msdn官方對它的解釋據說也是不清不楚

        }       

            Thread t = new Thread(new ParameterizedThreadStart(Run));

            MyPram _p = new MyPram() { context = SynchronizationContext.Current, parm = "Hello World" };

            t.Start(_p);

        void Run(object obj) 

            MyPram p = obj as MyPram;

            p.context.Post(SetTextValue, p.parm);

        void SetTextValue(object obj) 

    public class MyPram 

        public SynchronizationContext context { set; get; }

        public object parm { set; get; }

4.利用BackgroundWorker --最偷懶的辦法(Winform/Silverlight通用)

BackgroundWorker會在主線程之外,另開一個背景線程,我們可以把一些處理放在背景線程中處理,完成之後,背景線程會把結果傳遞給主線程,同時結束自己。

using System.ComponentModel;

            //MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());

            using (BackgroundWorker bw = new BackgroundWorker())

                bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

                bw.DoWork += new DoWorkEventHandler(bw_DoWork);

                bw.RunWorkerAsync("Hello World");

        void bw_DoWork(object sender, DoWorkEventArgs e)

            e.Result = e.Argument;//這裡隻是簡單的把參數當做結果傳回,當然您也可以在這裡做複雜的處理後,再傳回自己想要的結果(這裡的操作是在另一個線程上完成的)

        void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

            //這時背景線程已經完成,并傳回了主線程,是以可以直接使用UI控件了

            this.textBox1.Text = e.Result.ToString();

    }    

5.Dispatcher.BeginInvoke--Silverlight的獨門秘籍 

溫故而知新:WinForm/Silverlight多線程程式設計中如何更新UI控件的值
溫故而知新:WinForm/Silverlight多線程程式設計中如何更新UI控件的值

代碼

using System.Windows.Controls;

using System.Windows.Input;

    public partial class MainPage : UserControl

        public MainPage()

        private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

            Thread t = new Thread(SetTextValue);

        void SetTextValue(object text) 

            this.Dispatcher.BeginInvoke(() => { this.txt.Text = text.ToString(); });            

繼續閱讀