本文討論 .NET Framework 如何處理從 C# 和 Visual Basic 程式碼呼叫由 Windows 執行階段或 Windows 執行階段元件提供的物件的方式。
在 .NET Framework 中,您可以預設從多個線程存取任何物件,而不需要特殊處理。 您只需要對象的參考。 在 Windows 執行時間中,這類物件稱為 agile。 大部分的 Windows 執行時間類別都是敏捷式的,但少數類別則不是,甚至敏捷式類別可能需要特殊處理。
在可能的情況下,Common Language Runtime(CLR)會將來自其他來源的物件,例如 Windows 執行階段,當作 .NET Framework 物件來處理。
如果對象實作 IAgileObject 介面,或具有 MarshalingBehaviorAttribute 属性搭配 MarshalingType.Agile,CLR 會將它視為敏捷式。
如果 CLR 能夠將來自初始線程的呼叫封送到目標對象的線程上下文,它就會透明地執行。
如果物件具有 MarshalingBehaviorAttribute 屬性,且 MarshalingType.None,類別就不會提供封送處理資訊。 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
您可以使用按鈕 發送器 屬性,或 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 Runtime 物件的存留期,是由執行緒的存留期決定。 在視窗關閉之後,請勿嘗試存取UI線程上的物件。
如果您藉由繼承 XAML 控件或撰寫一組 XAML 控件來建立自己的控件,則控件是敏捷的,因為它是 .NET Framework 物件。 不過,如果呼叫其基類或組成類別的成員,或呼叫繼承的成員,這些成員會在從UI線程以外的任何線程呼叫時擲回例外狀況。
無法封送處理的類別
未提供封送處理資訊的 Windows 執行時間類別具有 MarshalingBehaviorAttribute 屬性,MarshalingType.None。 這類程式類別的文件會列出其屬性中的「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 物件來切換呼叫。