在多執行緒環境中使用 Windows 執行階段物件

本文說明 .NET Framework 如何處理從 C# 和 Visual Basic 程式碼到 Windows 執行階段或 Windows 執行階段元件所提供物件的呼叫。

在 .NET Framework 中,預設情況下您可以從多個執行緒存取任何物件,而無需特殊處理。 您只需要對該物件的參考。 在 Windows 執行階段中,這類物件稱為敏捷。 大多數 Windows 執行階段類別都是敏捷的,但也有少數類別不是,甚至敏捷類別也可能需要特殊處理。

只要有可能,通用語言執行平台 (CLR) 就會將來自其他來源 (例如 Windows 執行階段) 的物件視為 .NET Framework 物件:

以下各節說明了此行為對各種來源物件的影響。

來自用 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.NoneMarshalingBehaviorAttribute 屬性。 此類別的文件會在其屬性中列出「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 物件來分派呼叫。

另請參閱

C# 指南

Visual Basic 指南