单线程的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有效)
using System; using System.Threading; using System.Windows.Forms; namespace ThreadTest{ public partial class Form1 : Form { delegate void D( object obj); public Form1() { InitializeComponent(); } private void btnSet_Click( object sender, EventArgs e) { Thread t = new Thread( new ParameterizedThreadStart(SetTextBoxValue)); t.Start( " Hello World " ); } void SetTextBoxValue( object obj) { if (textBox1.InvokeRequired) { D d = new D(DelegateSetValue); textBox1.Invoke(d,obj); } else { this .textBox1.Text = obj.ToString(); } } void DelegateSetValue( object obj) { this .textBox1.Text = obj.ToString(); } }}
3.利用SynchronizationContext上下文 -- 最神秘的方法(Winform/Silverlight能用)
之所以说它神秘,是因为msdn官方对它的解释据说也是不清不楚
using System; using System.Threading; using System.Windows.Forms; namespace ThreadTest{ public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnSet_Click( object sender, EventArgs e) { 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) { this .textBox1.Text = obj.ToString(); } } public class MyPram { public SynchronizationContext context { set ; get ; } public object parm { set ; get ; } }}
4.利用BackgroundWorker --最偷懒的办法(Winform/Silverlight通用)
BackgroundWorker会在主线程之外,另开一个后台线程,我们可以把一些处理放在后台线程中处理,完成之后,后台线程会把结果传递给主线程,同时结束自己。
using System; using System.ComponentModel; using System.Windows.Forms; namespace ThreadTest{ public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnSet_Click( object sender, EventArgs e) { // 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) { // MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString()); e.Result = e.Argument; // 这里只是简单的把参数当做结果返回,当然您也可以在这里做复杂的处理后,再返回自己想要的结果(这里的操作是在另一个线程上完成的) } void bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { // 这时后台线程已经完成,并返回了主线程,所以可以直接使用UI控件了 this .textBox1.Text = e.Result.ToString(); // MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString()); } } }
5.Dispatcher.BeginInvoke--Silverlight的独门秘籍
代码
using System.Threading; using System.Windows.Controls; using System.Windows.Input; namespace ThreadTest{ public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } private void LayoutRoot_MouseLeftButtonDown( object sender, MouseButtonEventArgs e) { Thread t = new Thread(SetTextValue); t.Start( " Hello World " ); } void SetTextValue( object text) { this .Dispatcher.BeginInvoke(() => { this .txt.Text = text.ToString(); }); } }}