本文讨论 .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)”。但是,如果未在 UI 线程上调用其中一些敏捷类的成员(如 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 控件来创建您自己的控件,则该控件是敏捷的,因为它是一个 .NET Framework 对象。但是,如果该控件调用其基类或构成类的成员,或者,如果您调用继承的成员,则当从 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 运行时 组件中的对象
默认情况下,该组件中可激活的类都是敏捷类。但是,利用 C++,可以对线程模型和封送处理行为进行大量控制。如本文前面所述,CLR 可识别敏捷类,在类不是敏捷类时尝试封送调用,并在类没有封送处理信息时引发 InvalidCastException 异常。
对于在 UI 线程上运行的对象(在从 UI 线程之外的线程调用这些对象时,它们将引发异常),可以使用 UI 线程的 CoreDispatcher 对象来调度调用。