Uso di oggetti Windows Runtime in un ambiente a thread multipli

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

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

Laddove 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ò effettuare il marshalling di una chiamata dal thread in cui è stato effettuato 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 di marshalling. CLR non è in grado di effettuare 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 provenienti da varie origini.

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

Tutti i tipi del componente che possono essere attivati sono agili per impostazione predefinita.

Nota

L'agilità non implica thread safety. Sia in Windows Runtime che in .NET Framework, la maggior parte delle classi non è thread-safe perché thread safety ha un costo delle 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 di Windows Runtime

La maggior parte delle classi in Windows Runtime è agile e CLR li considera agile. La documentazione di queste classi elenca "MarshalingBehaviorAttribute(Agile)" tra gli attributi della classe. Tuttavia, i membri di alcune di queste classi Agile, ad esempio i controlli XAML, generano eccezioni se non vengono chiamate 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 al pulsante in modo sicuro usando la relativa proprietà Dispatcher o la Dispatcher proprietà di qualsiasi oggetto presente nel contesto del thread dell'interfaccia utente, ad esempio la pagina in cui si trova il pulsante. Il codice seguente usa il metodo RunAsync dell'oggetto CoreDispatcher per inviare la chiamata al 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 Dispatcher proprietà 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 dalla durata del thread. Non tentare di accedere agli oggetti in un thread dell'interfaccia utente dopo la chiusura della finestra.

Se crei un controllo personalizzato 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 i membri della classe di base o delle classi costitutive o se si chiamano membri ereditati, tali membri genereranno eccezioni quando vengono chiamate da qualsiasi thread, ad eccezione del thread dell'interfaccia utente.

Classi che non possono essere sottoposto a marshalling

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

Il codice seguente crea un oggetto Fotocamera CaptureUI nel thread dell'interfaccia utente e quindi tenta di impostare una proprietà dell'oggetto da un thread del pool di thread. CLR non è in grado di effettuare il marshalling della chiamata e 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 Fotocamera CaptureUI elenca anche "ThreadingAttribute(STA)" tra gli attributi della classe, perché deve essere creata in un contesto a thread singolo, ad esempio il thread dell'interfaccia utente.

Se si vuole accedere all'oggetto Fotocamera CaptureUI da un altro thread, è possibile memorizzare nella cache l'oggetto CoreDispatcher per il thread dell'interfaccia utente e usarlo in un secondo momento per inviare la chiamata su tale thread. In alternativa, puoi ottenere il dispatcher da un oggetto XAML, ad esempio la pagina, come illustrato nel 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 Agile, tenta di effettuare il marshalling delle chiamate quando le classi non sono agile e genera un'eccezione System.InvalidCastException quando una classe non dispone di informazioni di 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