Usando objetos do Windows Runtime em um ambiente de vários threads

Este artigo discute a maneira como o .NET Framework lida com chamadas de código C# e Visual Basic para objetos fornecidos pelo Tempo de Execução do Windows ou pelos componentes do Tempo de Execução do Windows.

No .NET Framework, você pode acessar qualquer objeto de vários threads por padrão, sem tratamento especial. Tudo o que você precisa é de uma referência ao objeto. No Tempo de Execução do Windows, esses objetos são chamados de ágeis. A maioria das classes do Tempo de Execução do Windows é ágil, mas algumas classes não são, e até mesmo classes ágeis podem exigir tratamento especial.

Sempre que possível, o CLR (Common Language Runtime) trata objetos de outras fontes, como o Tempo de Execução do Windows, como se fossem objetos do .NET Framework:

As seções a seguir descrevem os efeitos desse comportamento em objetos de várias fontes.

Objetos de um componente do Tempo de Execução do Windows escrito em C# ou Visual Basic

Todos os tipos no componente que podem ser ativados são ágeis por padrão.

Observação

Agilidade não implica segurança de thread. No Tempo de Execução do Windows e no .NET Framework, a maioria das classes não é thread-safe porque a segurança de thread tem um custo de desempenho e a maioria dos objetos nunca é acessada por vários threads. É mais eficiente sincronizar o acesso a objetos individuais (ou usar classes thread-safe) somente quando necessário.

Ao criar um componente do Tempo de Execução do Windows, você pode substituir o padrão. Consulte a interface ICustomQueryInterface e a interface IAgileObject .

Objetos do Tempo de Execução do Windows

A maioria das classes no Tempo de Execução do Windows é ágil e o CLR as trata como ágeis. A documentação dessas classes lista "MarshalingBehaviorAttribute(Agile)" entre os atributos de classe. No entanto, os membros de algumas dessas classes ágeis, como controles XAML, lançam exceções se não forem chamados no thread da interface do usuário. Por exemplo, o código a seguir tenta usar um thread em segundo plano para definir uma propriedade do botão que foi clicado. A propriedade Content do botão gera uma exceção.

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

Você pode acessar o botão com segurança usando sua propriedade Dispatcher ou a Dispatcher propriedade de qualquer objeto que exista no contexto do thread da interface do usuário (como a página em que o botão está). O código a seguir usa o método RunAsync do objeto CoreDispatcher para expedir a chamada no thread da interface do usuário.

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

Observação

A Dispatcher propriedade não gera uma exceção quando é chamada de outro thread.

O tempo de vida de um objeto do Tempo de Execução do Windows criado no thread da interface do usuário é limitado pelo tempo de vida do thread. Não tente acessar objetos em um thread de interface do usuário depois que a janela for fechada.

Se você criar seu próprio controle herdando um controle XAML ou compondo um conjunto de controles XAML, seu controle será ágil porque é um objeto .NET Framework. No entanto, se ele chamar membros de sua classe base ou classes constituintes, ou se você chamar membros herdados, esses membros gerarão exceções quando forem chamados de qualquer thread, exceto o thread da interface do usuário.

Classes que não podem ser organizadas

As classes do Tempo de Execução do Windows que não fornecem informações de marshaling têm o atributo MarshalingBehaviorAttribute com MarshalingType.None. A documentação dessa classe lista "MarshalingBehaviorAttribute(None)" entre seus atributos.

O código a seguir cria um objeto CameraCaptureUI no thread da interface do usuário e, em seguida, tenta definir uma propriedade do objeto de um thread do pool de threads. O CLR não consegue empacotar a chamada e gera uma exceção System.InvalidCastException com uma mensagem indicando que o objeto pode ser usado somente no contexto de threading em que foi criado.

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

A documentação de CameraCaptureUI também lista "ThreadingAttribute(STA)" entre os atributos da classe, pois ele deve ser criado em um contexto de thread único, como o thread da interface do usuário.

Se você quiser acessar o objeto CameraCaptureUI de outro thread, poderá armazenar em cache o objeto CoreDispatcher para o thread da interface do usuário e usá-lo posteriormente para despachar a chamada nesse thread. Ou você pode obter o dispatcher de um objeto XAML, como a página, conforme mostrado no código a seguir.

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 um componente do Tempo de Execução do Windows escrito em C++

Por padrão, as classes no componente que podem ser ativadas são ágeis. No entanto, o C++ permite uma quantidade significativa de controle sobre modelos de threading e comportamento de marshaling. Conforme descrito anteriormente neste artigo, o CLR reconhece classes ágeis, tenta empacotar chamadas quando as classes não são ágeis e gera uma exceção System.InvalidCastException quando uma classe não tem informações de marshaling.

Para objetos que são executados no thread da interface do usuário e geram exceções quando são chamados de um thread diferente do thread da interface do usuário, você pode usar o objeto CoreDispatcher do thread da interface do usuário para expedir a chamada.

Confira também

Guia do C#

Guia do Visual Basic