Verwenden von Windows-Runtime-Objekten in einer Multithread-Umgebung
In diesem Artikel wird erläutert, wie .NET Framework Aufrufe von C#- und Visual Basic-Code für Objekte verarbeitet, die vom Windows-Runtime oder von Windows-Runtime Komponenten bereitgestellt werden.
Im .NET Framework können Sie standardmäßig auf jedes Objekt aus mehreren Threads zugreifen, ohne dass spezielle Verarbeitung erfolgt. Sie benötigen lediglich einen Verweis auf das Objekt. In der Windows-Runtime werden solche Objekte als agil bezeichnet. Die meisten Windows-Runtime Klassen sind agil, aber einige Klassen sind nicht, und sogar agile Klassen erfordern möglicherweise eine spezielle Handhabung.
Nach Möglichkeit behandelt die Common Language Runtime (CLR) Objekte aus anderen Quellen, z. B. die Windows-Runtime, als wären sie .NET Framework-Objekte:
Wenn das Objekt die IAgileObject-Schnittstelle implementiert oder das MarshalingBehaviorAttribute-Attribut mit MarshalingType.Agile aufweist, behandelt die CLR sie als agil.
Wenn CLR einen Aufruf aus dem Thread marshallen kann, in dem er an den Threadingkontext des Zielobjekts vorgenommen wurde, ist dies transparent.
Wenn das Objekt über das MarshalingBehaviorAttribute-Attribut mit MarshalingType.None verfügt, stellt die Klasse keine Marshallinginformationen bereit. Die CLR kann den Aufruf nicht marshallen, sodass eine InvalidCastException-Ausnahme mit einer Meldung ausgelöst wird, die angibt, dass das Objekt nur im Threadingkontext verwendet werden kann, in dem es erstellt wurde.
In den folgenden Abschnitten werden die Auswirkungen dieses Verhaltens auf Objekte aus verschiedenen Quellen beschrieben.
Objekte aus einer Windows-Runtime Komponente, die in C# oder Visual Basic geschrieben wurde
Alle Typen in der Komponente, die aktiviert werden können, sind standardmäßig agil.
Hinweis
Agilität impliziert keine Threadsicherheit. Sowohl im Windows-Runtime als auch im .NET Framework sind die meisten Klassen nicht threadsicher, da die Threadsicherheit leistungseinbußen hat, und die meisten Objekte werden nie von mehreren Threads aufgerufen. Es ist effizienter, den Zugriff auf einzelne Objekte (oder threadsichere Klassen) nur bei Bedarf zu synchronisieren.
Wenn Sie eine Windows-Runtime Komponente erstellen, können Sie die Standardeinstellung außer Kraft setzen. Siehe die ICustomQueryInterface-Schnittstelle und die IAgileObject-Schnittstelle .
Objekte aus der Windows-Runtime
Die meisten Klassen in der Windows-Runtime sind agil, und die CLR behandelt sie als agil. Die Dokumentation für diese Klassen listet "MarshalingBehaviorAttribute(Agile)" unter den Klassenattributen auf. Allerdings lösen die Member einiger dieser agilen Klassen, z. B. XAML-Steuerelemente, Ausnahmen aus, wenn sie nicht im UI-Thread aufgerufen werden. Der folgende Code versucht beispielsweise, einen Hintergrundthread zu verwenden, um eine Eigenschaft der Schaltfläche festzulegen, auf die geklickt wurde. Die Inhaltseigenschaft der Schaltfläche löst eine Ausnahme aus.
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
Sie können sicher auf die Schaltfläche zugreifen, indem Sie dessen Dispatcher-Eigenschaft oder die Dispatcher
Eigenschaft eines Objekts verwenden, das im Kontext des UI-Threads vorhanden ist (z. B. die Seite, auf der sich die Schaltfläche befindet). Der folgende Code verwendet die RunAsync-Methode des CoreDispatcher-Objekts, um den Aufruf im UI-Thread zu verteilen.
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
Hinweis
Die Dispatcher
Eigenschaft löst keine Ausnahme aus, wenn sie von einem anderen Thread aufgerufen wird.
Die Lebensdauer eines Windows-Runtime Objekts, das im UI-Thread erstellt wird, ist an die Lebensdauer des Threads gebunden. Versuchen Sie nicht, auf Objekte in einem UI-Thread zuzugreifen, nachdem das Fenster geschlossen wurde.
Wenn Sie ein eigenes Steuerelement erstellen, indem Sie ein XAML-Steuerelement erben oder eine Gruppe von XAML-Steuerelementen erstellen, ist Ihr Steuerelement agil, da es sich um ein .NET Framework-Objekt handelt. Wenn sie jedoch Member der Basisklasse oder der konstituierenden Klassen aufruft oder geerbte Member aufrufen, lösen diese Member Ausnahmen aus, wenn sie von einem beliebigen Thread mit Ausnahme des UI-Threads aufgerufen werden.
Klassen, die nicht gemarstet werden können
Windows-Runtime Klassen, die keine Marshaling-Informationen bereitstellen, weisen die MarshalingBehaviorAttribute-Attribut mit MarshalingType.None. Die Dokumentation für eine solche Klasse listet "MarshalingBehaviorAttribute(None)" unter seinen Attributen auf.
Der folgende Code erstellt ein CameraCaptureUI-Objekt im UI-Thread und versucht dann, eine Eigenschaft des Objekts aus einem Threadpoolthread festzulegen. Die CLR kann den Aufruf nicht marshallen und löst eine System.InvalidCastException-Ausnahme mit einer Meldung aus, die angibt, dass das Objekt nur im Threadingkontext verwendet werden kann, in dem es erstellt wurde.
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
Die Dokumentation für CameraCaptureUI listet auch "ThreadingAttribute(STA)" unter den Attributen der Klasse auf, da sie in einem einzelthreadigen Kontext wie dem UI-Thread erstellt werden muss.
Wenn Sie von einem anderen Thread aus auf das CameraCaptureUI-Objekt zugreifen möchten, können Sie das CoreDispatcher-Objekt für den UI-Thread zwischenspeichern und später verwenden, um den Aufruf für diesen Thread zu verteilen. Oder Sie können den Verteiler aus einem XAML-Objekt wie der Seite abrufen, wie im folgenden Code dargestellt.
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
Objekte aus einer in C++ geschriebenen Windows-Runtime Komponente
Standardmäßig sind Klassen in der Komponente, die aktiviert werden können, agil. C++ ermöglicht jedoch eine erhebliche Kontrolle über Threadingmodelle und Marshallingverhalten. Wie weiter oben in diesem Artikel beschrieben, erkennt die CLR agile Klassen, versucht, Aufrufe zu marshallen, wenn Klassen nicht agil sind, und löst eine System.InvalidCastException-Ausnahme aus, wenn eine Klasse keine Marshallinginformationen enthält.
Bei Objekten, die im UI-Thread ausgeführt werden und Ausnahmen auslösen, wenn sie von einem anderen Thread als dem UI-Thread aufgerufen werden, können Sie das CoreDispatcher-Objekt des UI-Threads verwenden, um den Aufruf zu verteilen.