Windows 窗體依賴於基本的 Win32 消息。因此,它繼承了典型的 Windows 編程要求:只有創建窗口的線程可以處理它的消息。在 .Net 框架 2.0 中,調用錯誤的線程總會觸發一個 Windows 窗體方面的異常。因此,當在另一個 線程中調用窗體或控件時,必須將該調用封送到正確的所屬線程中。Windows 窗 體有內置的支持,可以用來擺脫這個困境,方法是用 Control 基類實現 ISynchronizeInvoke 接口,其定義如下:
public interface ISynchronizeInvoke
{
bool InvokeRequired {get;}
IAsyncResult BeginInvoke(Delegate method,object[] args);
object EndInvoke(IAsyncResult result);
object Invoke(Delegate method,object[] args);
}
Invoke 方法接受針對所屬線程中的 方法的委托,並且將調用從正在調用的線程封送到該線程。因為您可能並不總是 知道自己是否真的在正確的線程中執行,所以通過使用 InvokeRequired 屬性, 您可以進行查詢,從而弄清楚是否需要調用 Invoke 方法。問題是,使用 ISynchronizeInvoke 將會大大增加編程模型的復雜性,因此較好的方法常常是將 帶有 ISynchronizeInvoke 接口的交互封裝在控件或窗體中,它們會自動地按需 使用 ISynchronizeInvoke。
例如,為了替代公開 Text 屬性的 Label 控 件,您可以定義從 Label 派生的 SafeLabel 控件,如圖 10 所示。SafeLabel 重寫了其基類的 Text 屬性。在其 get 和 set 中,它檢查 Invoke 是否是必需 的。如果是這樣,則它需要使用一個委托來訪問此屬性。該實現僅僅調用了基類 屬性的實現,不過是在正確的線程上。因為 SafeLabel 只定義這些方法,所以它 們可以通過委托進行調用,它們是匿名方法很好的候選者。SafeLabel 傳遞這樣 的委托,以便將匿名方法作為其 Text 屬性的安全實現包裝到 Invoke 方法中。
委托推理
C# 編譯器從匿名方法指派推理哪個委托類型將要實例化的能 力是一個非常重要的功能。實際上,它還提供了另一個叫做委托推理的 C# 2.0 功能。委托推理允許直接給委托變量指派方法名,而不需要先使用委托對象包裝 它。例如下面的 C# 1.1 代碼:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = new SomeDelegate(SomeMethod);
del();
}
void SomeMethod()
{...}
}
現在,您可以編寫下面的代碼來代替前面的代碼片斷:
class SomeClass
{
delegate void SomeDelegate ();
public void InvokeMethod()
{
SomeDelegate del = SomeMethod;
del();
}
void SomeMethod()
{...}
}
當將一個方法名指派給委托時, 編譯器首先推理該委托的類型。然後,編譯器根據此名稱檢驗是否存在一個方法 ,並且它的簽名是否與推理的委托類型相匹配。最後,編譯器創建一個推理委托 類型的新對象,以便包裝此方法,並將其指派給該委托。如果該類型是一個具體 的委托類型(即除了抽象類型 Delegate 之外的其他類型),則編譯器只能推理 委托類型。委托推理的確是一個非常有用的功能,它可以使代碼變得簡練而優雅 。
我相信,作為 C# 2.0 中的慣例,您會使用委托推理,而不是以前的委 托實例化方法。例如,下面的代碼說明了如何在不顯式地創建一個 ThreadStart 委托的情況下啟動一個新的線程:
public class MyClass
{
void ThreadMethod()
{...}
public void LauchThread()
{
Thread workerThread = new Thread (ThreadMethod);
workerThread.Start();
}
}
當啟動一個異步調用並提供一個完整的回調方法時,可以使用一對委 托推理,如圖 11 所示。首先,指定異步調用的方法名來異步調用一個匹配的委 托。然後調用 BeginInvoke,提供完整的回調方法名而不是 AsyncCallback 類型 的委托。