在多執行緒環境中使用 Windows 執行階段物件
本文說明 .NET Framework 如何處理從 C# 和 Visual Basic 程式碼到 Windows 執行階段或 Windows 執行階段元件所提供物件的呼叫。
在 .NET Framework 中,預設情況下您可以從多個執行緒存取任何物件,而無需特殊處理。 您只需要對該物件的參考。 在 Windows 執行階段中,這類物件稱為敏捷。 大多數 Windows 執行階段類別都是敏捷的,但也有少數類別不是,甚至敏捷類別也可能需要特殊處理。
只要有可能,通用語言執行平台 (CLR) 就會將來自其他來源 (例如 Windows 執行階段) 的物件視為 .NET Framework 物件:
如果物件實作 IAgileObject 介面,或具有 MarshalingType.Agile 的 MarshalingBehaviorAttribute 屬性,則 CLR 會將其視為敏捷物件。
如果 CLR 可以將呼叫從執行該呼叫的執行緒封送到目標物件的執行緒內容,那麼它會透明地執行此作業。
如果物件具有 MarshalingType.None 的 MarshalingBehaviorAttribute 屬性,則該類別不提供封送資訊。 CLR 無法封送該呼叫,因此它會擲回 InvalidCastException 例外狀況,並顯示一則訊息,指示該物件只能在建立它的執行緒內容中使用。
以下各節說明了此行為對各種來源物件的影響。
來自用 C# 或 Visual Basic 撰寫的 Windows 執行階段元件的物件
元件中所有可以啟動的類型預設都是敏捷的。
注意
敏捷性並不代表執行緒安全。 在 Windows 執行階段和 .NET Framework 中,大多數類別都不是執行緒安全的,因為執行緒安全性會帶來效能成本,而且大多數物件永遠不會被多個執行緒存取。 僅在必要時同步對各個物件的存取權 (或使用執行緒安全類別) 會讓效率更高。
當您製作 Windows 執行階段元件時,可以覆寫預設值。 請參閱 ICustomQueryInterface 介面和 IAgileObject 介面。
來自 Windows 執行階段的物件
Windows 執行階段中的大多數類別都是敏捷的,且 CLR 會將它們視為敏捷的。 這些類別的文件會在類別屬性中列出「MarshalingBehaviorAttribute(Agile)」。 但是,其中一些敏捷類別的成員 (例如 XAML 控制項) 如果不在 UI 執行緒上嚕較,則會擲回例外狀況。 例如,以下程式碼嘗試使用背景執行緒來設定所點選按鈕的屬性。 按鈕的 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 控制項來建立自己的控制項,則您的控制項會是敏捷的,因為它是 .NET Framework 物件。 但是,如果它呼叫其基底類別或組成類別的成員,或者如果您呼叫繼承的成員,則從 UI 執行緒之外的任何執行緒呼叫這些成員時,都會擲回例外狀況。
無法封送的類別
不提供封送資訊的 Windows 執行階段類別具有包含 MarshalingType.None 的 MarshalingBehaviorAttribute 屬性。 此類別的文件會在其屬性中列出「MarshalingBehaviorAttribute(None)」。
以下程式碼會在 UI 執行緒上建立 CameraCaptureUI 物件,然後嘗試從執行緒集區執行緒設定該物件的屬性。 CLR 無法封送該呼叫,並會擲回 System.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 執行階段元件的物件
預設情況下,元件中可啟動的類別是敏捷的。 不過,C++ 允許對執行緒模型和封送行為進行大量控制。 如本文前面所述,CLR 能識別敏捷類別,在類別不敏捷時嘗試封送呼叫,並在類別沒有封送資訊時擲回 System.InvalidCastException 例外狀況。
對於在 UI 執行緒上執行,並從 UI 執行緒以外的執行緒呼叫時擲回例外狀況的物件,可以使用 UI 執行緒的 CoreDispatcher 物件來分派呼叫。