Verwenden von Windows-Runtime-Objekten in einer Multithread-Umgebung

In diesem Artikel wird erläutert, wie der .NET Framework Aufrufe von C#- und Visual Basic-Code für Objekte verarbeitet, die vom Windows-Runtime oder von Windows-Runtime Komponenten bereitgestellt werden.

In .NET Framework können Sie standardmäßig von mehreren Threads aus ohne besondere Maßnahmen auf beliebige Objekte zugreifen. Sie benötigen dazu lediglich einen Verweis auf das Objekt. Im Windows-Runtime werden solche Objekte als agile bezeichnet. Die meisten Windows-Runtime Klassen sind agil, aber einige Klassen nicht, und sogar agile Klassen erfordern möglicherweise eine spezielle Behandlung.

Nach Möglichkeit behandelt die Common Language Runtime (CLR) Objekte aus anderen Quellen, z. B. der Windows-Runtime, als wären sie .NET Framework-Objekte:

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 ist

Alle aktivierbaren Typen in der Komponente sind standardmäßig agile.

Hinweis

Agilität impliziert nicht zwangsläufig Threadsicherheit. Sowohl in der Windows-Runtime als auch im .NET Framework sind die meisten Klassen nicht threadsicher, da die Threadsicherheit leistungsbedingt ist und auf die meisten Objekte nie von mehreren Threads zugegriffen wird. Es ist effizienter, den Zugriff auf einzelne Objekte nur bei Bedarf zu synchronisieren (oder in diesen Fällen threadsichere Klassen zu verwenden).

Wenn Sie eine Windows-Runtime-Komponente erstellen, können Sie die Standardeinstellung überschreiben. Siehe die ICustomQueryInterface-Schnittstelle und die IAgileObject-Schnittstelle .

Objekte aus dem 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)“ bei den Klassenattributen auf. Die Member einiger dieser Agile-Klassen, wie etwa XAML-Steuerelemente, lösen jedoch Ausnahmen aus, wenn sie nicht im UI-Thread aufgerufen werden. Der folgende Code versucht beispielsweise, einen Hintergrundthread zu verwenden, um eine Eigenschaft der angeklickten Schaltfläche festzulegen. Die Content-Eigenschaft 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 die Dispatcher-Eigenschaft oder die Dispatcher Eigenschaft eines beliebigen 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 für den UI-Thread zu senden.

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, wird durch die Lebensdauer des Threads begrenzt. Versuchen Sie nicht, auf Objekte in einem UI-Thread zuzugreifen, nachdem das Fenster geschlossen wurde.

Wenn Sie ein eigenes Steuerelement durch Vererbung von einem XAML-Steuerelement oder durch Zusammenstellen einer Sammlung von XAML-Steuerelementen erstellen, ist Ihr Steuerelement agile, da es sich um ein .NET Framework-Objekt handelt. Wenn jedoch Member seiner Basisklasse oder konstituierender Klassen oder aber vererbte Member aufgerufen werden, lösen die betreffenden Member Ausnahmen aus, wenn sie von einem anderen als dem UI-Thread aufgerufen werden.

Klassen, die nicht gemarshallt werden können

Windows-Runtime Klassen, die keine Marshallinformationen bereitstellen, verfügen über das MarshalingBehaviorAttribute-Attribut mit MarshalingType.None. Die Dokumentation für derartige Klassen listet „MarshalingBehaviorAttribute(None)“ bei den 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 es in einem Einzelthreadkontext wie dem UI-Thread erstellt werden muss.

Wenn Sie über einen anderen Thread 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 senden. Alternativ können Sie den Dispatcher eines XAML-Objekts abrufen, wie etwa der Seite, wie im folgenden Codebeispiel zu sehen.

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 Windows-Runtime-Komponente, die in C++ geschrieben ist

Standardmäßig sind alle aktivierbaren Klassen in der Komponente agile. C++ erlaubt jedoch die Steuerung der Threadingmodelle und des Marshallingverhaltens in erheblichem Umfang. 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 Marshallinformationen enthält.

Für Objekte, 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 senden.

Weitere Informationen

Leitfaden für C#

Leitfaden für Visual Basic