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 el Windows Runtime o por componentes de Windows Runtime.

En .NET Framework, puede acceder a cualquier objeto desde varios subprocesos de forma predeterminada, sin ningún control especial. Todo lo necesario es una referencia al objeto. En el Windows Runtime, estos objetos se denominan agile. La mayoría de las clases 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 el 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 calcular las referencias de una llamada desde el subproceso donde se creó en el contexto del subproceso del objeto de destino, lo hace de manera transparente.

  • Si el objeto tiene el atributo MarshalingBehaviorAttribute con MarshalingType.None, la clase no proporciona información de serialización. CLR no puede calcular las referencias de 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 distintos 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 el 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. Resulta más eficaz para sincronizar el acceso a objetos individuales (o utilizar clases seguras para subprocesos) según sea necesario.

Al crear un componente de Windows Runtime, puede invalidar el valor predeterminado. Consulte la interfaz ICustomQueryInterface y la interfaz IAgileObject .

Objetos del Windows Runtime

La mayoría de las clases del 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, lanzan excepciones si no se llaman en el subproceso de 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 Windows Runtime que se crea en el subproceso de la interfaz de usuario está limitada 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 crea su propio control mediante la herencia de un control XAML o la composición de un conjunto de controles XAML, el control es ágil porque es un objeto de .NET Framework. Sin embargo, si llama a los miembros de su clase base o sus clases constituyentes, o si se llama a los miembros heredados, esos miembros lanzarán excepciones cuando se les llame desde cualquier subproceso, salvo el subproceso de interfaz de usuario.

Clases de las que no se pueden calcular las referencias

Windows Runtime clases que no proporcionan información de serialización tienen el atributo MarshalingBehaviorAttribute con MarshalingType.None. La documentación de este tipo de clase enumera "MarshalingBehaviorAttribute(Agile)" entre sus atributos.

El código siguiente crea un objeto CameraCaptureUI en el subproceso de 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 interfaz de usuario y usarlo más adelante para enviar la llamada en ese subproceso. O bien, puede 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 el comportamiento del suprocesamiento de modelos y el cálculo de referencias. 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.

En el caso de 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.

Consulte también

Guía de C#

Guía de Visual Basic