程式設計環境要求:VS2008/FX2.0
衆所周知,從VS2005/FX2.0起,在多線程環境下是不允許跨線程修改主線程上視窗控件的。
例如:
private void button1_Click( object sender, EventArgs e)
{
Thread t = new Thread( new ThreadStart(CrossThreadCall));
t.Start();
}
public void CrossThreadCall()
{
Text = " test " ;
}
将直接導緻異常:
未處理 System.InvalidOperationException
Message="線程間操作無效: 從不是建立控件“Form1”的線程通路它。"
Source="System.Windows.Forms"
StackTrace:
在 System.Windows.Forms.Control.get_Handle()
在 System.Windows.Forms.Control.set_WindowText(String value)
在 System.Windows.Forms.Form.set_WindowText(String value)
在 System.Windows.Forms.Control.set_Text(String value)
在 System.Windows.Forms.Form.set_Text(String value)
在 delegate_test1.Form1.CrossThreadCall() 位置 f:/app/delegate_test1/delegate_test1/Form1.cs:行号 26
在 System.Threading.ThreadHelper.ThreadStart_Context(Object state)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
在 System.Threading.ThreadHelper.ThreadStart()
通用的解決方法是使用Control.Invoke方法來調用一個Delegate,進而安全地跨線程調用。
例如:
public void CrossThreadCall()
{
Invoke( new void_d(CrossThreadCall_Local));
}
void CrossThreadCall_Local()
{
Text = " test " ;
}
public delegate void void_d();
但是這樣的缺點是要不得不為每個調用編寫一個Invoke跳闆,還要額外聲明一個委托類型,實在不怎麼優雅。
于是我們想到用匿名函數來寫。我的第一反應是:
Invoke( delegate { Text = " test " ; });
可惜不行。編譯壓根就沒通過,寫着:
無法将 匿名方法 轉換為類型“System.Delegate”,因為它不是委托類型
無語,delegate竟然不是委托類型?
等我把Google翻了一遍後,找到了答案。
The problem the user is seeing is that the Thread ctor accepts a specific delegate -- the ThreadStart delegate. The C# compiler will check and make sure your anonymous method matches the signature of the ThreadStart delegate and, if so, produces the proper code under-the-covers to create the ThreadStart delegate for you.
But Control.Invoke is typed as accepting a "Delegate". This means it can accept any delegate-derived type. The example above shows an anonymous method that has a void return type and takes no parameters. It's possible to have a number of delegate-derived types that match that signature (such as MethodInvoker and ThreadStart -- just as an example). Which specific delegate should the C# compiler use? There's no way for it to infer the exact delegate type so the compiler complains with an error.
也就是說,對于Thread.ctor()來說,由于接受的是一個ThreadStart委托,編譯器便可以将匿名函數與ThreadStart委托類型比對,最後能夠正确編譯。
而對于Control.Invoke()來說,任何的代理類型都是可接受的,也就是說ThreadStart和MethodInvoker都是可以接受的類型。這樣編譯器反而不知道應該用哪個代理去比對匿名函數了,導緻了編譯錯誤的發生。
知道了原因,問題就很容易解決了。我們隻需要加上MethodInvoker這個wrapper就能使用匿名函數了。
Invoke( new MethodInvoker( delegate { Text = " test " ; }));
或者更簡單地,用Lambda表達式來解決問題:
Invoke( new MethodInvoker(() => Text = " test " ));
希望本文能夠幫助有同樣困惑的朋友。