Utilisation des objets Windows Runtime dans un environnement multithread
Cet article décrit la façon dont .NET Framework gère les appels du code C# et Visual Basic aux objets fournis par 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 gestion spéciale. Tout ce dont vous avez besoin est une référence à l’objet. Dans Windows Runtime, ces objets sont appelés agiles. La plupart des classes Windows Runtime sont agiles, mais quelques classes ne 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 d’autres sources, tels que Windows Runtime, comme s’ils étaient des objets .NET Framework :
Si l’objet implémente l’interface IAgileObject ou a l’attribut MarshalingBehaviorAttribute avec MarshalingType.Agile, le CLR le traite comme agile.
Si CLR peut marshaler un appel à partir du thread où il a été effectué dans le contexte de thread de l’objet cible, il le fait de manière transparente.
Si l’objet a l’attribut MarshalingBehaviorAttribute avec MarshalingType.None, la classe ne fournit pas d’informations de marshaling. Le CLR ne peut pas marshaler l’appel. Il lève donc une exception InvalidCastException avec un message indiquant que l’objet ne peut être utilisé que dans le contexte de thread où il a été créé.
Les sections suivantes décrivent les effets de ce comportement sur les objets provenant de différentes 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.
Remarque
L’agilité n’implique pas la sécurité des threads. Dans Windows Runtime et .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 si nécessaire.
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 de Windows Runtime
La plupart des classes de Windows Runtime sont agiles et le CLR les traite comme agiles. La documentation de ces classes répertorie « MarshalingBehaviorAttribute(Agile) » parmi les attributs de classe. Toutefois, les membres de certaines de ces classes agiles, telles que les contrôles XAML, lèvent des exceptions si elles ne sont pas appelées 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 qui a été 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
Remarque
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 une fois la fenêtre fermée.
Si vous créez votre propre contrôle en hériter d’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 constituantes, ou si vous appelez des membres hérités, ces membres lèvent des exceptions lorsqu’ils sont appelés à partir d’un thread à l’exception du thread d’interface utilisateur.
Classes qui ne peuvent pas être marshalées
Les classes Windows Runtime qui ne fournissent pas d’informations de marshaling ont l’attribut MarshalingBehaviorAttribute avec MarshalingType.None. La documentation d’une telle 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 où 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 monothread 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++ permet un contrôle important sur les modèles de thread et le comportement de marshaling. Comme décrit précédemment 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 n’a pas d’informations 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 le thread d’interface utilisateur, vous pouvez utiliser l’objet CoreDispatcher du thread d’interface utilisateur pour distribuer l’appel.