在多线程环境中使用 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 线程上未调用它们时会引发异常。 例如,以下代码尝试使用后台线程设置单击的按钮的属性。 按钮 内容 属性引发异常。

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 运行时类具有 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 对象来调度这些调用。

另请参阅

C# 指南

Visual Basic 指南