Uso di oggetti Windows Runtime in un ambiente a thread multipli

Questo articolo spiega il modo in cui .NET Framework gestisce le chiamate dal codice C# e Visual Basic agli oggetti forniti da Windows Runtime o dai relativi componenti.

In .NET Framework è possibile accedere a qualsiasi oggetto da più thread per impostazione predefinita, senza una gestione speciale. Tutto ciò che serve è un riferimento all'oggetto. In Windows Runtime questi oggetti sono chiamati agili. La maggior parte delle classi di Windows Runtime è agile, ma alcune classi non lo sono e anche le classi agili possono richiedere una gestione speciale.

Ove possibile, Common Language Runtime (CLR) gestisce oggetti di altre origini, ad esempio Windows Runtime, come se fossero oggetti .NET Framework:

  • Se l'oggetto implementa l'interfaccia IAgileObject o ha l'attributo MarshalingBehaviorAttribute con MarshalingType.Agile, CLR lo considera agile.

  • Se CLR può eseguire il marshalling di una chiamata dal thread in cui è stata eseguita al contesto di threading dell'oggetto di destinazione, lo esegue in modo trasparente.

  • Se l'oggetto ha l'attributo MarshalingBehaviorAttribute con MarshalingType.None, la classe non fornisce informazioni sul marshalling. CLR non è in grado di eseguire il marshalling della chiamata, pertanto genera un'eccezione InvalidCastException con un messaggio che indica che l'oggetto può essere usato solo nel contesto di threading in cui è stato creato.

Le sezioni seguenti descrivono gli effetti di questo comportamento sugli oggetti di varie origini.

Oggetti da un componente Windows Runtime scritto in C# o Visual Basic

Tutti i tipi del componente che è possibile attivare sono agili per impostazione predefinita.

Nota

L'agilità non implica la sicurezza del thread. In Windows Runtime e in .NET Framework la maggior parte delle classi non è thread-safe perché la thread safety implica un prezzo in termini di prestazioni e la maggior parte degli oggetti non è mai accessibile da più thread. È più efficiente sincronizzare l'accesso a singoli oggetti (o usare classi thread-safe) solo se necessario.

Quando si crea un componente Windows Runtime, è possibile eseguire l'override del valore predefinito. Vedere l'interfaccia ICustomQueryInterface e l'interfaccia IAgileObject.

Oggetti da Windows Runtime

La maggior parte delle classi in Windows Runtime è agile e CLR le considera come tali. La documentazione di queste classi elenca "MarshalingBehaviorAttribute(Agile)" tra gli attributi di classe. Tuttavia, i membri di alcune di queste classi agili, ad esempio i controlli XAML, generano eccezioni se non vengono chiamati nel thread dell'interfaccia utente. Ad esempio, il codice seguente tenta di usare un thread in background per impostare una proprietà del pulsante su cui è stato fatto clic. La proprietà Content del pulsante genera un'eccezione.

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

È possibile accedere in sicurezza al pulsante usando la relativa proprietà Dispatcher o la proprietà Dispatcher di qualsiasi oggetto che esiste nel contesto del thread dell'interfaccia utente (ad esempio la pagina in cui si trova la pagina). Il codice seguente usa il metodo RunAsync dell'oggetto CoreDispatcher per inviare la chiamata sul thread dell'interfaccia utente.

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 proprietà Dispatcher non genera un'eccezione quando viene chiamata da un altro thread.

La durata di un oggetto Windows Runtime creato nel thread dell'interfaccia utente è vincolata alla durata del thread. Non tentare di accedere agli oggetti in un thread dell'interfaccia utente dopo la chiusura della finestra.

Se si crea un controllo ereditando un controllo XAML o componendo un set di controlli XAML, il controllo è agile perché si tratta di un oggetto .NET Framework. Tuttavia, se chiama membri della classe di base o classi costitutive o se si chiamano membri ereditati, tali membri genereranno eccezioni quando vengono chiamati da qualsiasi thread, ad eccezione del thread dell'interfaccia utente.

Classi di cui non è possibile eseguire il marshalling

Le classi Windows Runtime che non forniscono informazioni sul marshalling hanno l'attributo MarshalingBehaviorAttribute con MarshalingType.None. La documentazione per una classe di questo tipo elenca "MarshalingBehaviorAttribute(None)" tra gli attributi.

Il codice seguente crea un oggetto CameraCaptureUI sul thread dell'interfaccia utente, quindi tenta di impostare una proprietà dell'oggetto da un thread del pool. CLR non è in grado di eseguire il marshalling della chiamata, pertanto genera un'eccezione System.InvalidCastException con un messaggio che indica che l'oggetto può essere usato solo nel contesto di threading in cui è stato creato.

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 documentazione per CameraCaptureUI elenca anche "ThreadingAttribute(STA)" tra gli attributi della classe perché deve essere creato in un contesto a thread singolo come il thread dell'interfaccia utente.

Se si vuole accedere all'oggetto CameraCaptureUI da un altro thread, è possibile memorizzare nella cache l'oggetto CoreDispatcher per il thread dell'interfaccia utente e lo usa in seguito per inviare la chiamata su quel thread. In alternativa, è possibile ottenere il dispatcher da un oggetto XAML, ad esempio la pagina, come mostra il codice seguente.

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

Oggetti di un componente Windows Runtime scritto in C++

Per impostazione predefinita, le classi nel componente che possono essere attivate sono agili. Tuttavia, C++ consente una quantità significativa di controllo sui modelli di threading e sul comportamento di marshalling. Come descritto in precedenza in questo articolo, CLR riconosce le classi agili, tenta di eseguire il marshalling delle chiamate quando le classi non sono agili e genera un'eccezione System.InvalidCastException quando una classe non dispone di informazioni sul marshalling.

Per gli oggetti eseguiti nel thread dell'interfaccia utente e generano eccezioni quando vengono chiamate da un thread diverso dal thread dell'interfaccia utente, è possibile usare l'oggetto CoreDispatcher del thread dell'interfaccia utente per inviare la chiamata.

Vedi anche

Guida di C#

Guida a Visual Basic