Compartilhar via


Usando os objetos do Windows Runtime em um ambiente multithread

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

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 Windows Runtime, esses objetos são chamados agile. A maioria das classes do Windows Runtime é á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 Windows Runtime, como se fossem objetos do .NET Framework:

  • Se o objeto implementar a interface IAgileObject ou tiver o atributo MarshalingBehaviorAttribute com MarshalingType.Agile, o CLR o tratará como ágil.

  • Se o CLR puder fazer marshaling de uma chamada do thread em que ela foi feita para o contexto de threading do objeto de destino, ela o fará de forma transparente.

  • Se o objeto tiver o atributo MarshalingBehaviorAttribute com MarshalingType.None, a classe não fornecerá informações de marshaling. O CLR não pode realizar marshaling da chamada, portanto, ele gera um InvalidCastException exceção com uma mensagem indicando que o objeto só pode ser usado no contexto de threading em que foi criado.

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

Objetos de um componente do Windows Runtime 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 Windows Runtime e no .NET Framework, a maioria das classes não são thread safe porque a segurança do 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 conforme necessário.

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

Objetos do Windows Runtime

A maioria das classes no Windows Runtime é á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, geram 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 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 propriedade Dispatcher de qualquer objeto que exista no contexto da thread da interface do usuário (como a página onde o botão está). O código a seguir usa o método RunAsync do objeto coreDispat cher 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 propriedade Dispatcher não gera uma exceção quando é chamada de outro thread.

O tempo de vida de um objeto do Windows Runtime 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 após o fechamento da janela.

Se você criar seu próprio controle herdando um controle XAML ou compondo um conjunto de controles XAML, o 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 lançarão exceções quando forem chamados de qualquer thread, exceto o thread da interface do usuário.

Classes que não podem ser marshaladas

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

O código a seguir cria um objeto CameraCaptureUI no thread de UI e tenta definir uma propriedade do objeto a partir de um thread do pool de threads. O CLR não consegue fazer marshaling da chamada e gera uma exceção System.InvalidCastException com uma mensagem indicando que o objeto só pode ser usado no contexto de thread 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 do CameraCaptureUI também lista "ThreadingAttribute(STA)" entre os atributos da classe, pois ela deve ser criada 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 expedir 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 Windows Runtime que é 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 os modelos de threading e o comportamento de marshaling. Conforme descrito anteriormente neste artigo, o CLR reconhece classes ágeis, tenta realizar o marshaling de chamadas quando as classes não são ágeis e lança uma exceção System.InvalidCastException quando uma classe não possui 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 de interface do usuário para expedir a chamada.

Consulte Também

Guia do C#

Guia do Visual Basic