Utilisation des objets Windows Runtime dans un environnement multithread

Cet article décrit la façon dont .NET Framework gère les appels à partir de code C# et Visual Basic vers des objets fournis par le Windows Runtime ou par les composants Windows Runtime.

Dans le .NET Framework, vous pouvez accéder à n’importe quel objet à partir de plusieurs threads par défaut, sans traitement spécial. Vous avez simplement besoin d’une référence à l’objet. Dans le Windows Runtime, ces objets sont appelés agiles. La plupart des classes Windows Runtime sont agiles, mais certaines ne le sont pas, et même les classes agiles peuvent nécessiter une gestion spéciale.

Dans la mesure du possible, le Common Language Runtime (CLR) traite les objets provenant d’autres sources, telles que les Windows Runtime, comme s’il s’agissait d’objets .NET Framework :

Les sections suivantes décrivent les effets de ce comportement sur les objets de diverses sources.

Objets d’un composant Windows Runtime écrit en C# ou Visual Basic

Tous les types du composant qui peuvent être activés sont agiles par défaut.

Notes

L’agilité n’implique pas la sécurité des threads. Dans le Windows Runtime et le .NET Framework, la plupart des classes ne sont pas thread safe, car la sécurité des threads a un coût de performances et la plupart des objets ne sont jamais accessibles par plusieurs threads. Il est plus efficace de synchroniser l’accès à des objets individuels (ou d’utiliser des classes thread-safe) uniquement en cas de besoin.

Lorsque vous créez un composant Windows Runtime, vous pouvez remplacer la valeur par défaut. Consultez l’interface ICustomQueryInterface et l’interface IAgileObject .

Objets du Windows Runtime

La plupart des classes du Windows Runtime sont agiles et le CLR les traite comme agiles. La documentation pour ces classes répertorie « MarshalingBehaviorAttribute(Agile) » parmi les attributs de classe. Toutefois, les membres de certaines de ces classes agiles, tels que les contrôles XAML, lèvent des exceptions s’ils ne sont pas appelés sur le thread d’interface utilisateur. Par exemple, le code suivant tente d’utiliser un thread d’arrière-plan pour définir une propriété du bouton sur lequel l’utilisateur a cliqué. La propriété Content du bouton lève une exception.

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

Vous pouvez accéder au bouton en toute sécurité à l’aide de sa propriété Dispatcher ou de la Dispatcher propriété de n’importe quel objet qui existe dans le contexte du thread d’interface utilisateur (par exemple, la page sur laquelle se trouve le bouton). Le code suivant utilise la méthode RunAsync de l’objet CoreDispatcher pour distribuer l’appel sur le thread d’interface utilisateur.

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

Notes

La Dispatcher propriété ne lève pas d’exception lorsqu’elle est appelée à partir d’un autre thread.

La durée de vie d’un objet Windows Runtime créé sur le thread d’interface utilisateur est limitée par la durée de vie du thread. N’essayez pas d’accéder aux objets sur un thread d’interface utilisateur après la fermeture de la fenêtre.

Si vous créez votre propre contrôle en héritant un contrôle XAML ou en composant un ensemble de contrôles XAML, votre contrôle est agile, car il s’agit d’un objet .NET Framework. Toutefois, s’il appelle des membres de sa classe de base ou des classes qui le composent, ou si vous appelez des membres hérités, ces membres lèvent des exceptions quant ils sont appelés à partir de tout thread autre que le thread d’interface utilisateur.

Classes qui ne peuvent pas être marshalées

Windows Runtime classes qui ne fournissent pas d’informations de marshaling ont l’attribut MarshalingBehaviorAttribute avec MarshalingType.None. La documentation pour cette classe répertorie « MarshalingBehaviorAttribute(None) » parmi ses attributs.

Le code suivant crée un objet CameraCaptureUI sur le thread d’interface utilisateur, puis tente de définir une propriété de l’objet à partir d’un thread de pool de threads. Le CLR ne peut pas marshaler l’appel et lève une exception System.InvalidCastException avec un message indiquant que l’objet ne peut être utilisé que dans le contexte de thread dans lequel il a été créé.

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 documentation de CameraCaptureUI répertorie également « ThreadingAttribute(STA) » parmi les attributs de la classe, car elle doit être créée dans un contexte à thread unique tel que le thread d’interface utilisateur.

Si vous souhaitez accéder à l’objet CameraCaptureUI à partir d’un autre thread, vous pouvez mettre en cache l’objet CoreDispatcher pour le thread d’interface utilisateur et l’utiliser ultérieurement pour distribuer l’appel sur ce thread. Vous pouvez également obtenir le répartiteur à partir d’un objet XAML tel que la page, comme illustré dans le code suivant.

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

Objets d’un composant Windows Runtime écrit en C++

Par défaut, les classes du composant qui peuvent être activées sont agiles. Toutefois, C++ autorise un contrôle significatif sur les modèles de thread et le comportement de marshaling. Comme décrit plus haut dans cet article, le CLR reconnaît les classes agiles, tente de marshaler les appels lorsque les classes ne sont pas agiles et lève une exception System.InvalidCastException lorsqu’une classe ne dispose d’aucune information de marshaling.

Pour les objets qui s’exécutent sur le thread d’interface utilisateur et lèvent des exceptions lorsqu’ils sont appelés à partir d’un thread autre que celui de l’interface utilisateur, vous pouvez utiliser l’objet CoreDispatcher du thread d’interface utilisateur pour distribuer l’appel.

Voir aussi

C# Guide

Visual Basic Guide