Usar objetos de Windows Runtime en un entorno multiproceso
En este artículo se describe la forma en que .NET Framework controla las llamadas desde código de C# y Visual Basic a objetos proporcionados por Windows Runtime o por componentes de Windows Runtime.
En .NET Framework, puede acceder a cualquier objeto desde varios subprocesos de forma predeterminada, sin control especial. Todo lo que necesita es una referencia al objeto . En Windows Runtime, estos objetos se denominan agile. La mayoría de las clases de Windows Runtime son ágiles, pero algunas clases no son, e incluso las clases ágiles pueden requerir un control especial.
Siempre que sea posible, Common Language Runtime (CLR) trata objetos de otros orígenes, como Windows Runtime, como si fueran objetos de .NET Framework:
Si el objeto implementa la interfaz IAgileObject o tiene el atributo MarshalingBehaviorAttribute con MarshalingType.Agile, CLR lo trata como ágil.
Si CLR puede serializar una llamada desde el subproceso donde se realizó al contexto de subproceso del objeto de destino, lo hace de forma transparente.
Si el objeto tiene el atributo MarshalingBehaviorAttribute con MarshalingType.None, la clase no proporciona información de serialización. CLR no puede serializar la llamada, por lo que produce una excepción InvalidCastException con un mensaje que indica que el objeto solo se puede usar en el contexto de subproceso donde se creó.
En las secciones siguientes se describen los efectos de este comportamiento en objetos de varios orígenes.
Objetos de un componente de Windows Runtime escrito en C# o Visual Basic
Todos los tipos del componente que se pueden activar son ágiles de forma predeterminada.
Nota:
La agilidad no implica la seguridad de los subprocesos. En Windows Runtime y .NET Framework, la mayoría de las clases no son seguras para subprocesos porque la seguridad de subprocesos tiene un costo de rendimiento y la mayoría de los objetos nunca tienen acceso a ellos varios subprocesos. Es más eficaz sincronizar el acceso a objetos individuales (o usar clases seguras para subprocesos) solo según sea necesario.
Al crear un componente de Windows Runtime, puede invalidar el valor predeterminado. Vea la interfaz ICustomQueryInterface y la interfaz IAgileObject .
Objetos de Windows Runtime
La mayoría de las clases de Windows Runtime son ágiles y CLR las trata como ágiles. La documentación de estas clases enumera "MarshalingBehaviorAttribute(Agile)" entre los atributos de clase. Sin embargo, los miembros de algunas de estas clases ágiles, como los controles XAML, inician excepciones si no se les llama en el subproceso de la interfaz de usuario. Por ejemplo, el código siguiente intenta usar un subproceso en segundo plano para establecer una propiedad del botón en el que se hizo clic. La propiedad Content del botón produce una excepción.
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
Puede acceder al botón de forma segura mediante su propiedad Dispatcher o la Dispatcher
propiedad de cualquier objeto que exista en el contexto del subproceso de la interfaz de usuario (como la página en la que se encuentra el botón). El código siguiente usa el método RunAsync del objeto CoreDispatcher para enviar la llamada en el subproceso de la interfaz de usuario.
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
Nota:
La Dispatcher
propiedad no produce una excepción cuando se llama desde otro subproceso.
La duración de un objeto de Windows Runtime que se crea en el subproceso de la interfaz de usuario está limitado por la duración del subproceso. No intente acceder a objetos en un subproceso de interfaz de usuario después de cerrar la ventana.
Si creas tu propio control heredando un control XAML o redactando un conjunto de controles XAML, el control es ágil porque es un objeto de .NET Framework. Sin embargo, si llama a miembros de su clase base o clases constituyentes, o si llama a miembros heredados, esos miembros producirán excepciones cuando se llamen desde cualquier subproceso excepto el subproceso de interfaz de usuario.
Clases que no se pueden serializar
Las clases de Windows Runtime que no proporcionan información de serialización tienen el atributo MarshalingBehaviorAttribute con MarshalingType.None. La documentación de esta clase enumera "MarshalingBehaviorAttribute(None)" entre sus atributos.
El código siguiente crea un objeto CameraCaptureUI en el subproceso de la interfaz de usuario y, a continuación, intenta establecer una propiedad del objeto a partir de un subproceso del grupo de subprocesos. CLR no puede calcular las referencias de la llamada y produce una excepción System.InvalidCastException con un mensaje que indica que el objeto solo se puede usar en el contexto de subproceso donde se creó.
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
La documentación de CameraCaptureUI también enumera "ThreadingAttribute(STA)" entre los atributos de la clase, ya que debe crearse en un contexto de un solo subproceso, como el subproceso de la interfaz de usuario.
Si desea acceder al objeto CameraCaptureUI desde otro subproceso, puede almacenar en caché el objeto CoreDispatcher para el subproceso de la interfaz de usuario y usarlo más adelante para enviar la llamada en ese subproceso. O bien, puedes obtener el distribuidor de un objeto XAML, como la página, como se muestra en el código siguiente.
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
Objetos de un componente de Windows Runtime escrito en C++
De forma predeterminada, las clases del componente que se pueden activar son ágiles. Sin embargo, C++ permite una cantidad significativa de control sobre los modelos de subprocesos y el comportamiento de serialización. Como se ha descrito anteriormente en este artículo, CLR reconoce clases ágiles, intenta serializar las llamadas cuando las clases no son ágiles y produce una excepción System.InvalidCastException cuando una clase no tiene información de serialización.
Para los objetos que se ejecutan en el subproceso de interfaz de usuario y producen excepciones cuando se llaman desde un subproceso distinto del subproceso de interfaz de usuario, puede usar el objeto CoreDispatcher del subproceso de la interfaz de usuario para enviar la llamada.