在多執行緒環境中使用 Windows 執行階段物件
本文將討論 .NET Framework 處理從 C# 和 Visual Basic 程式碼呼叫 Windows 執行階段 或 Windows 執行階段 元件所提供之物件的方式。
根據預設,在 .NET Framework 中,您可以從多個執行緒存取任何物件,而不需要特殊處理。您需要的只是物件的參考。在 Windows 執行階段 中,這類物件稱為 Agile。大部分的 Windows 執行階段 類別都是 Agile,但有些類別不是,而 Agile 類別也可能需要特殊處理。
在可能的情況下,Common Language Runtime (CLR) 會將其他來源 (例如 Windows 執行階段) 的物件視為是 .NET Framework 物件:
如果物件實作 IAgileObject 介面,或具有含 MarshalingType.Agile 的 MarshalingBehaviorAttribute 屬性,CLR 就會將它視為是 Agile。
當呼叫是來自建立呼叫的執行緒時,如果 CLR 可以將它封送處理至目標物件的執行緒內容,就會以通透的方式執行。
如果物件具有含 MarshalingType.None 的 MarshalingBehaviorAttribute 屬性,類別就不會提供封送處理資訊。CLR 無法封送處理呼叫,因此它會擲回含訊息的 InvalidCastException 例外狀況,指出只能在建立該物件的執行緒內容中使用該物件。
下列各節將從各種來源的物件說明這個行為的效用。
以 C# 或 Visual Basic 撰寫的 Windows 執行階段 元件的物件
根據預設,元件中可以啟用的所有型別都是 Agile。
注意事項 |
---|
靈活度並不表示執行緒安全。在 Windows 執行階段 和 .NET Framework 中,大部分的類別並不具備執行緒安全,因為執行緒安全有效能成本,而且大部分的物件從未由多個執行緒存取。更有效率的方法是只在必要時同步處理個別物件的存取 (或使用安全執行緒類別)。 |
當您撰寫 Windows 執行階段 元件時,可以覆寫預設值。請參閱 ICustomQueryInterface 介面和 IAgileObject 介面。
Windows 執行階段 的物件
Windows 執行階段 中的大部分類別都是 Agile 類別,CLR 會將它們視為是 Agile。這些類別的文件會將「MarshalingBehaviorAttribute(Agile)」列在類別的屬性中。不過,如果沒有在 UI 執行緒呼叫某些這類 Agile 類別的成員 (例如 XAML 控制項),它們會擲回例外狀況。例如,下列程式碼會嘗試使用背景執行緒來設定所按之按鈕的屬性。按鈕的 Content 屬性會擲回例外狀況。
private async void Button_Click_2(object sender, RoutedEventArgs e)
{
Button b = (Button) sender;
await Task.Run(() => {
b.Content += ".";
});
}
Private Async Sub Button_Click_2(sender As Object, e As RoutedEventArgs)
Dim b As Button = CType(sender, Button)
Await Task.Run(Sub()
b.Content &= "."
End Sub)
End Sub
您可以使用按鈕的 Dispatcher 屬性,或存在於 UI 執行緒內容中 (例如按鈕所在的網頁) 的任何物件的 Dispatcher 屬性,安全地存取按鈕。下列程式碼會使用 CoreDispatcher 物件的 RunAsync 方法來發送 UI 執行緒上的呼叫。
private async void Button_Click_2(object sender, RoutedEventArgs e)
{
Button b = (Button) sender;
await b.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
() => {
b.Content += ".";
});
}
Private Async Sub Button_Click_2(sender As Object, e As RoutedEventArgs)
Dim b As Button = CType(sender, Button)
Await b.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
Sub()
b.Content &= "."
End Sub)
End Sub
注意事項 |
---|
從另一個執行緒呼叫 Dispatcher 屬性時,它並不會擲回例外狀況。 |
在 UI 執行緒上建立的 Windows 執行階段 物件的存留期取決於執行緒的存留期。在視窗關閉之後,不要嘗試存取 UI 執行緒上的物件。
如果您透過繼承 XAML 控制項或是透過撰寫一組 XAML 控制項來建立自己的控制項,您的控制項就會是 Agile,因為它是 .NET Framework 物件。不過,如果它呼叫其基底類別 (Base Class) 或組成類別的成員,或者如果您呼叫繼承的成員,當從任何執行緒 (除了 UI 執行緒外) 呼叫這些成員時,這些成員就會擲回例外狀況。
無法封送處理的類別
不會提供封送處理資訊的 Windows 執行階段 類別具有含 MarshalingType.None 的 MarshalingBehaviorAttribute 屬性。這種類別的文件會將「MarshalingBehaviorAttribute(None)」列在其屬性中。
下列程式碼會在 UI 執行緒上建立 CameraCaptureUI 物件,然後試圖從執行緒集區執行緒來設定此物件的屬性。CLR 無法封送處理呼叫,並且會擲回含訊息的 InvalidCastException 例外狀況,指出只能在建立該物件的執行緒內容中使用該物件。
Windows.Media.Capture.CameraCaptureUI ccui;
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
ccui = new Windows.Media.Capture.CameraCaptureUI();
await Task.Run(() => {
ccui.PhotoSettings.AllowCropping = true;
});
}
Private ccui As Windows.Media.Capture.CameraCaptureUI
Private Async Sub Button_Click_1(sender As Object, e As RoutedEventArgs)
ccui = New Windows.Media.Capture.CameraCaptureUI()
Await Task.Run(Sub()
ccui.PhotoSettings.AllowCropping = True
End Sub)
End Sub
CameraCaptureUI 的文件也會將「ThreadingAttribute(STA)」列在此類別的屬性中,因為它必須在單一執行緒內容 (例如 UI 執行緒) 中建立。
如果您想要從另一個執行緒存取 CameraCaptureUI 物件,可以快取 UI 執行緒的 CoreDispatcher 物件,並在稍後用它來發送該執行緒上的呼叫。或者您也可以從 XAML 物件 (例如網頁) 取得發送器,如下列程式碼所示。
Windows.Media.Capture.CameraCaptureUI ccui;
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
ccui = new Windows.Media.Capture.CameraCaptureUI();
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
() => {
ccui.PhotoSettings.AllowCropping = true;
});
}
Dim ccui As Windows.Media.Capture.CameraCaptureUI
Private Async Sub Button_Click_3(sender As Object, e As RoutedEventArgs)
ccui = New Windows.Media.Capture.CameraCaptureUI()
Await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
Sub()
ccui.PhotoSettings.AllowCropping = True
End Sub)
End Sub
以 C++ 撰寫的 Windows 執行階段 元件的物件
根據預設,元件中可以啟用的類別都是 Agile。不過,C++ 允許透過執行緒模型和封送處理行為的大量控制項。如本文先前所述,CLR 會辨識 Agile 類別,若不是 Agile 類別,它會嘗試封送處理呼叫,並在類別沒有封送處理資訊時擲回 InvalidCastException 例外狀況。
如果物件是在 UI 執行緒上執行,並且從該 UI 執行緒以外的其他執行緒呼叫時會擲回例外狀況,您可以使用 UI 執行緒的 CoreDispatcher 物件來發送呼叫。