Threading-Modell

Windows Presentation Foundation (WPF) soll Entwicklern bei Problemen mit Threading helfen. Folglich muss die Mehrzahl der WPF Entwickler keine Schnittstelle schreiben, die mehr als einen Thread verwendet. Da Multithreadprogramme komplex und schwierig zu debuggen sind, sollten sie vermieden werden, wenn Singlethread-Lösungen vorhanden sind.

Unabhängig davon, wie gut entworfen wurde, kann kein Benutzeroberfläche Framework für jede Art von Problem eine Single Thread-Lösung bereitstellen. WPF wird geschlossen, aber es gibt immer noch Situationen, in denen mehrere Threads die Benutzeroberfläche (User Interface, UI) Reaktionsfähigkeit oder die Anwendungsleistung verbessern. In diesem Artikel werden zunächst einige Hintergrundinformationen angegeben, dann einige dieser Situationen beschrieben und am Ende einige Details auf niedriger Ebene besprochen.

Hinweis

In diesem Thema wird das Threading mithilfe der- BeginInvoke Methode für asynchrone Aufrufe erläutert. Sie können auch asynchrone Aufrufe durchführen, indem Sie die- InvokeAsync Methode aufrufen, die einen- Action Func<TResult> Parameter oder einen-Parameter annimmt. Die- InvokeAsync Methode gibt ein DispatcherOperation oder zurück DispatcherOperation<TResult> , das über eine- Task Eigenschaft verfügt. Sie können das await Schlüsselwort entweder mit dem DispatcherOperation oder dem zugeordneten verwenden Task . Wenn Sie synchron auf das Task warten müssen, das von DispatcherOperation oder DispatcherOperation<TResult> zurückgegeben wird, rufen Sie die DispatcherOperationWait-Erweiterungsmethode auf. Das Aufrufen von Task.Wait führt zu einem Deadlock. Weitere Informationen zum Task Ausführen von asynchronen Vorgängen mithilfe von finden Sie unter aufgabenbasierte asynchrone Programmierung. Die- Invoke Methode verfügt auch über über Ladungen, die einen Action oder Func<TResult> als Parameter annehmen. Mithilfe der-Methode können Sie Invoke synchrone Aufrufe durchführen, indem Sie einen-Delegaten übergeben Action Func<TResult> .

Übersicht und Verteiler

Anwendungen beginnen in der Regel WPF mit zwei Threads: einer für die Behandlung von Rendering und einem weiteren für die Verwaltung von Benutzeroberfläche . Der Renderingthread wird im Hintergrund ausgeblendet, während der Benutzeroberfläche Thread Eingaben empfängt, Ereignisse verarbeitet, den Bildschirm zeichnet und Anwendungscode ausführt. Die meisten Anwendungen verwenden einen einzelnen Benutzeroberfläche Thread, aber in manchen Situationen ist es am besten, mehrere zu verwenden. Dies wird weiter unten mit einem Beispiel erläutert.

Der Benutzeroberfläche Thread fügt Arbeitselemente in ein Objekt ein, das als bezeichnet wird Dispatcher . Vom Dispatcher werden Arbeitsaufgaben nach Priorität ausgewählt und jeweils vollständig ausgeführt. Jeder Benutzeroberfläche Thread muss mindestens einen aufweisen Dispatcher , und jeder Dispatcher kann Arbeitsaufgaben in genau einem Thread ausführen.

Der Trick, reaktionsfähige, benutzerfreundliche Anwendungen zu entwickeln, besteht darin, den Durchsatz zu maximieren, Dispatcher indem die Arbeitselemente gering gehalten werden. Auf diese Weise werden Elemente in der Warteschlange, die auf die Verarbeitung warten, nie abgelaufen Dispatcher . Jede spürbare Verzögerung zwischen Eingabe und Antwort kann für einen Benutzer frustrierend sein.

Wie sollen also WPF Anwendungen große Vorgänge verarbeiten? Was geschieht, wenn der Code eine aufwändige Berechnung beinhaltet oder eine Abfrage einer Datenbank auf einem Remoteserver ausführen muss? In der Regel besteht die Antwort darin, den großen Vorgang in einem separaten Thread zu behandeln, sodass der Thread nicht mehr in Benutzeroberfläche der Dispatcher Warteschlange steht. Wenn der große Vorgang fertiggestellt ist, kann er sein Ergebnis zur Anzeige an den Thread zurückmelden Benutzeroberfläche .

In der Vergangenheit ermöglicht Windows Benutzeroberfläche den Zugriff auf Elemente nur durch den Thread, der Sie erstellt hat. Dies bedeutet, dass ein Hintergrundthread bei einer Aufgabe mit langer Laufzeit kein Textfeld aktualisieren kann, wenn er abgeschlossen ist. Windows führt dies aus, um die Integrität der Komponenten sicherzustellen Benutzeroberfläche . Ein Listenfeld könnte merkwürdig aussehen, wenn sein Inhalt während des Zeichnens von einem Hintergrundthread aktualisiert werden würde.

WPF verfügt über einen integrierten gegenseitigen Ausschlussmechanismus, der diese Koordination erzwingt. Die meisten Klassen in werden WPF von abgeleitet DispatcherObject . Bei der Erstellung speichert ein einen DispatcherObject Verweis auf den, der mit Dispatcher dem aktuell laufenden Thread verknüpft ist. Tatsächlich verknüpft der den DispatcherObject Thread, der ihn erstellt. Während der Programmausführung DispatcherObject kann eine seine öffentliche Methode aufzurufen VerifyAccess . VerifyAccess überprüft den Dispatcher , der dem aktuellen Thread zugeordnet ist, und vergleicht ihn mit dem Dispatcher während der Konstruktion gespeicherten Verweis. Wenn Sie nicht identisch sind, wird von VerifyAccess eine Ausnahme ausgelöst. VerifyAccess soll am Anfang jeder Methode aufgerufen werden, die zu einer gehört DispatcherObject .

Wenn nur ein Thread das ändern kann Benutzeroberfläche , wie interagieren Hintergrundthreads mit dem Benutzer? Ein Hintergrund Thread kann den Benutzeroberfläche Thread auffordern, einen Vorgang in seinem Auftrag auszuführen. Dies erfolgt durch das Registrieren eines Arbeits Elements beim Dispatcher des Benutzeroberfläche Threads. Die Dispatcher -Klasse stellt zwei Methoden zum Registrieren von Arbeits Elementen bereit: Invoke und BeginInvoke . Beide Methoden planen einen Delegaten für die Ausführung ein. Invoke ist ein synchroner Aufruf – das heißt, er wird erst zurückgegeben, wenn der Benutzeroberfläche Thread die Ausführung des Delegaten tatsächlich abgeschlossen hat. BeginInvoke ist asynchron und wird sofort zurückgegeben.

DispatcherOrdnet die Elemente in der Warteschlange nach Priorität an. Es gibt zehn Ebenen, die angegeben werden können, wenn ein Element zur Dispatcher Warteschlange hinzugefügt wird. Diese Prioritäten werden in der- DispatcherPriority Enumeration beibehalten. Ausführliche Informationen zu DispatcherPriority Ebenen finden Sie in der Windows SDK-Dokumentation.

Threads in Aktion: Beispiele

Eine Singlethread-Anwendung mit einer Berechnung mit langer Laufzeit

Die meisten grafischen Benutzeroberflächen (GUIs) verbringen einen großen Teil ihrer Zeit im Leerlauf und warten auf Ereignisse, die als Reaktion auf Benutzerinteraktionen generiert werden. Bei sorgfältiger Programmierung kann diese Leerlaufzeit konstruktiv genutzt werden, ohne dass sich dies auf die Reaktionsfähigkeit von auswirkt Benutzeroberfläche . Das WPF Threading Modell lässt nicht zu, dass Eingaben einen Vorgang im Thread unterbrechen Benutzeroberfläche . Dies bedeutet, dass Sie in regelmäßigen Abständen zu den zurückkehren müssen Dispatcher , um ausstehende Eingabeereignisse zu verarbeiten, bevor Sie veraltet sind.

Betrachten Sie das folgende Beispiel:

Screenshot, der das Threading von Primzahlen zeigt.

Diese einfache Anwendung zählt ab drei aufwärts und sucht dabei nach Primzahlen. Wenn der Benutzer auf die Schaltfläche " Start " klickt, beginnt die Suche. Wenn das Programm eine Primzahl findet, wird die Benutzeroberfläche mit dieser Entdeckung aktualisiert. Der Benutzer kann die Suche zu jedem Zeitpunkt beenden.

Obwohl es sich um eine einfache Anwendung handelt, könnte die Suche nach Primzahlen endlos fortgesetzt werden, was einige Probleme bereitet. Wenn wir die gesamte Suche innerhalb des Click-Ereignis Handlers der Schaltfläche behandelt haben, würden wir dem Thread nie die Möglichkeit einräumen, Benutzeroberfläche andere Ereignisse zu verarbeiten. BenutzeroberflächeKann nicht auf Eingabe-oder Verarbeitungs Nachrichten reagieren. Sie würde nie neu zeichnen und nie auf Mausklicks auf die Schaltflächen reagieren.

Wir könnten die Suche nach Primzahlen in einem separaten Thread ausführen, aber dann hätten wir Synchronisierungsprobleme. Mit einem Singlethread-Ansatz können wir die Bezeichnung, die die größte gefundene Primzahl auflistet, direkt aktualisieren.

Wenn wir die Berechnungs Aufgabe in verwaltbare Blöcke aufteilen, können wir in regelmäßigen Abständen zu den Dispatcher -und-Prozess Ereignissen zurückkehren. Wir können WPF die Möglichkeit zum Neuzeichnen und Verarbeiten von Eingaben bieten.

Die beste Möglichkeit, die Verarbeitungszeit Zwischenberechnungen und Ereignis Behandlung aufzuteilen, besteht darin, die Berechnung über die zu verwalten Dispatcher . Mithilfe der- BeginInvoke Methode können wir Primzahlen Überprüfungen in derselben Warteschlange planen, Benutzeroberfläche aus der Ereignisse gezeichnet werden. In unserem Beispiel planen wir nur eine einzige Primzahlüberprüfung zu einem Zeitpunkt ein. Nach Abschluss der Primzahlüberprüfung planen wir sofort die nächste Überprüfung ein. Diese Überprüfung wird nur fortgesetzt, nachdem ausstehende Benutzeroberfläche Ereignisse behandelt wurden.

Screenshot, der die Verteiler-Warteschlange anzeigt

Microsoft Word führt die Rechtschreibprüfung mit diesem Mechanismus durch. Die Rechtschreibprüfung erfolgt im Hintergrund mithilfe der Leerlaufzeit des Benutzeroberfläche Threads. Sehen wir uns den Code an.

Das folgende Beispiel zeigt die XAML, die die Benutzeroberfläche erstellt.

<Window x:Class="SDKSamples.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Prime Numbers" Width="260" Height="75"
    >
  <StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
    <Button Content="Start"  
            Click="StartOrStop"
            Name="startStopButton"
            Margin="5,0,5,0"
            />
    <TextBlock Margin="10,5,0,0">Biggest Prime Found:</TextBlock>
    <TextBlock Name="bigPrime" Margin="4,5,0,0">3</TextBlock>
  </StackPanel>
</Window>

Im folgenden Beispiel wird das CodeBehind gezeigt.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Threading;

namespace SDKSamples
{
    public partial class Window1 : Window
    {
        public delegate void NextPrimeDelegate();

        //Current number to check
        private long num = 3;

        private bool continueCalculating = false;

        public Window1() : base()
        {
            InitializeComponent();
        }

        private void StartOrStop(object sender, EventArgs e)
        {
            if (continueCalculating)
            {
                continueCalculating = false;
                startStopButton.Content = "Resume";
            }
            else
            {
                continueCalculating = true;
                startStopButton.Content = "Stop";
                startStopButton.Dispatcher.BeginInvoke(
                    DispatcherPriority.Normal,
                    new NextPrimeDelegate(CheckNextNumber));
            }
        }

        public void CheckNextNumber()
        {
            // Reset flag.
            NotAPrime = false;

            for (long i = 3; i <= Math.Sqrt(num); i++)
            {
                if (num % i == 0)
                {
                    // Set not a prime flag to true.
                    NotAPrime = true;
                    break;
                }
            }

            // If a prime number.
            if (!NotAPrime)
            {
                bigPrime.Text = num.ToString();
            }

            num += 2;
            if (continueCalculating)
            {
                startStopButton.Dispatcher.BeginInvoke(
                    System.Windows.Threading.DispatcherPriority.SystemIdle,
                    new NextPrimeDelegate(this.CheckNextNumber));
            }
        }

        private bool NotAPrime = false;
    }
}
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Threading
Imports System.Threading

Namespace SDKSamples
    Partial Public Class MainWindow
        Inherits Window
        Public Delegate Sub NextPrimeDelegate()

        'Current number to check 
        Private num As Long = 3

        Private continueCalculating As Boolean = False

        Public Sub New()
            MyBase.New()
            InitializeComponent()
        End Sub

        Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs)
            If continueCalculating Then
                continueCalculating = False
                startStopButton.Content = "Resume"
            Else
                continueCalculating = True
                startStopButton.Content = "Stop"
                startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber))
            End If
        End Sub

        Public Sub CheckNextNumber()
            ' Reset flag.
            NotAPrime = False

            For i As Long = 3 To Math.Sqrt(num)
                If num Mod i = 0 Then
                    ' Set not a prime flag to true.
                    NotAPrime = True
                    Exit For
                End If
            Next

            ' If a prime number.
            If Not NotAPrime Then
                bigPrime.Text = num.ToString()
            End If

            num += 2
            If continueCalculating Then
                startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber))
            End If
        End Sub

        Private NotAPrime As Boolean = False
    End Class
End Namespace

Das folgende Beispiel zeigt den Ereignishandler für das Button .

private void StartOrStop(object sender, EventArgs e)
{
    if (continueCalculating)
    {
        continueCalculating = false;
        startStopButton.Content = "Resume";
    }
    else
    {
        continueCalculating = true;
        startStopButton.Content = "Stop";
        startStopButton.Dispatcher.BeginInvoke(
            DispatcherPriority.Normal,
            new NextPrimeDelegate(CheckNextNumber));
    }
}
Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs)
    If continueCalculating Then
        continueCalculating = False
        startStopButton.Content = "Resume"
    Else
        continueCalculating = True
        startStopButton.Content = "Stop"
        startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber))
    End If
End Sub

Neben dem Aktualisieren des Texts auf dem Button ist dieser Handler für die Planung der ersten Primzahlen Prüfung zuständig, indem der Warteschlange ein Delegat hinzugefügt wird Dispatcher . Nachdem der Ereignishandler seine Arbeit abgeschlossen hat, wählt der Dispatcher diesen Delegaten für die Ausführung aus.

Wie bereits erwähnt, BeginInvoke ist der Member, der Dispatcher zum Planen eines Delegaten für die Ausführung verwendet wird. In diesem Fall wählen wir die SystemIdle Priorität aus. Der Dispatcher führt diesen Delegaten nur aus, wenn keine wichtigen Ereignisse zu verarbeiten sind. Benutzeroberfläche-Reaktionsfähigkeit ist wichtiger als die Zahlenüberprüfung. Wir übergeben auch einen neuen Delegaten, der die Zahlenüberprüfungsroutine darstellt.

public void CheckNextNumber()
{
    // Reset flag.
    NotAPrime = false;

    for (long i = 3; i <= Math.Sqrt(num); i++)
    {
        if (num % i == 0)
        {
            // Set not a prime flag to true.
            NotAPrime = true;
            break;
        }
    }

    // If a prime number.
    if (!NotAPrime)
    {
        bigPrime.Text = num.ToString();
    }

    num += 2;
    if (continueCalculating)
    {
        startStopButton.Dispatcher.BeginInvoke(
            System.Windows.Threading.DispatcherPriority.SystemIdle,
            new NextPrimeDelegate(this.CheckNextNumber));
    }
}

private bool NotAPrime = false;
Public Sub CheckNextNumber()
    ' Reset flag.
    NotAPrime = False

    For i As Long = 3 To Math.Sqrt(num)
        If num Mod i = 0 Then
            ' Set not a prime flag to true.
            NotAPrime = True
            Exit For
        End If
    Next

    ' If a prime number.
    If Not NotAPrime Then
        bigPrime.Text = num.ToString()
    End If

    num += 2
    If continueCalculating Then
        startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber))
    End If
End Sub

Private NotAPrime As Boolean = False

Diese Methode überprüft, ob die nächste ungerade Zahl eine Primzahl ist. Wenn es sich um eine Primzahl handelt, aktualisiert die Methode das direkt, um die Ermittlung bigPrime TextBlock widerzuspiegeln. Dies ist möglich, da die Berechnung im selben Thread ausgeführt wird, der für die Erstellung der Komponente verwendet wurde. Hätten wir beschlossen, einen separaten Thread für die Berechnung zu verwenden, müssten wir einen komplizierteren Synchronisierungs Mechanismus verwenden und das Update im Thread ausführen Benutzeroberfläche . Wir werden diese Situation im Folgenden zeigen.

Den gesamten Quellcode für dieses Beispiel finden Sie im Beispiel für eine Single Thread-Anwendung mit Long-Running Berechnung .

Behandeln eines blockierenden Vorgangs mit einem Hintergrundthread

Die Behandlung von blockierenden Vorgängen in einer grafischen Anwendung kann schwierig sein. Wir wollen keine blockierenden Methoden von Ereignishandlern aufrufen, da die Anwendung sonst scheinbar einfriert. Wir können einen separaten Thread verwenden, um diese Vorgänge zu verarbeiten, aber wenn wir fertig sind, müssen wir mit dem Benutzeroberfläche Thread synchronisieren, da wir die GUI nicht direkt von unserem Arbeits Thread aus ändern können. Wir können Invoke oder verwenden BeginInvoke , um Delegaten in den Dispatcher des Benutzeroberfläche Threads einzufügen. Schließlich werden diese Delegaten mit der Berechtigung zum Ändern von Benutzeroberfläche Elementen ausgeführt.

In diesem Beispiel simulieren wir einen Remoteprozeduraufruf, der eine Wettervorhersage abruft. Wir verwenden zum Ausführen dieses Aufrufes einen separaten Arbeits Thread, und wir planen eine Aktualisierungs Methode im Dispatcher des Benutzeroberfläche Threads, wenn wir fertig sind.

Screenshot, in dem die Wetter Benutzeroberfläche angezeigt wird.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Threading;

namespace SDKSamples
{
    public partial class Window1 : Window
    {
        // Delegates to be used in placking jobs onto the Dispatcher.
        private delegate void NoArgDelegate();
        private delegate void OneArgDelegate(String arg);

        // Storyboards for the animations.
        private Storyboard showClockFaceStoryboard;
        private Storyboard hideClockFaceStoryboard;
        private Storyboard showWeatherImageStoryboard;
        private Storyboard hideWeatherImageStoryboard;

        public Window1(): base()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Load the storyboard resources.
            showClockFaceStoryboard =
                (Storyboard)this.Resources["ShowClockFaceStoryboard"];
            hideClockFaceStoryboard =
                (Storyboard)this.Resources["HideClockFaceStoryboard"];
            showWeatherImageStoryboard =
                (Storyboard)this.Resources["ShowWeatherImageStoryboard"];
            hideWeatherImageStoryboard =
                (Storyboard)this.Resources["HideWeatherImageStoryboard"];
        }

        private void ForecastButtonHandler(object sender, RoutedEventArgs e)
        {
            // Change the status image and start the rotation animation.
            fetchButton.IsEnabled = false;
            fetchButton.Content = "Contacting Server";
            weatherText.Text = "";
            hideWeatherImageStoryboard.Begin(this);

            // Start fetching the weather forecast asynchronously.
            NoArgDelegate fetcher = new NoArgDelegate(
                this.FetchWeatherFromServer);

            fetcher.BeginInvoke(null, null);
        }

        private void FetchWeatherFromServer()
        {
            // Simulate the delay from network access.
            Thread.Sleep(4000);

            // Tried and true method for weather forecasting - random numbers.
            Random rand = new Random();
            String weather;

            if (rand.Next(2) == 0)
            {
                weather = "rainy";
            }
            else
            {
                weather = "sunny";
            }

            // Schedule the update function in the UI thread.
            tomorrowsWeather.Dispatcher.BeginInvoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new OneArgDelegate(UpdateUserInterface),
                weather);
        }

        private void UpdateUserInterface(String weather)
        {
            //Set the weather image
            if (weather == "sunny")
            {
                weatherIndicatorImage.Source = (ImageSource)this.Resources[
                    "SunnyImageSource"];
            }
            else if (weather == "rainy")
            {
                weatherIndicatorImage.Source = (ImageSource)this.Resources[
                    "RainingImageSource"];
            }

            //Stop clock animation
            showClockFaceStoryboard.Stop(this);
            hideClockFaceStoryboard.Begin(this);

            //Update UI text
            fetchButton.IsEnabled = true;
            fetchButton.Content = "Fetch Forecast";
            weatherText.Text = weather;
        }

        private void HideClockFaceStoryboard_Completed(object sender,
            EventArgs args)
        {
            showWeatherImageStoryboard.Begin(this);
        }

        private void HideWeatherImageStoryboard_Completed(object sender,
            EventArgs args)
        {
            showClockFaceStoryboard.Begin(this, true);
        }
    }
}

Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Media.Imaging
Imports System.Windows.Shapes
Imports System.Windows.Threading
Imports System.Threading

Namespace SDKSamples
    Partial Public Class Window1
        Inherits Window
        ' Delegates to be used in placking jobs onto the Dispatcher.
        Private Delegate Sub NoArgDelegate()
        Private Delegate Sub OneArgDelegate(ByVal arg As String)

        ' Storyboards for the animations.
        Private showClockFaceStoryboard As Storyboard
        Private hideClockFaceStoryboard As Storyboard
        Private showWeatherImageStoryboard As Storyboard
        Private hideWeatherImageStoryboard As Storyboard

        Public Sub New()
            MyBase.New()
            InitializeComponent()
        End Sub

        Private Sub Window_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Load the storyboard resources.
            showClockFaceStoryboard = CType(Me.Resources("ShowClockFaceStoryboard"), Storyboard)
            hideClockFaceStoryboard = CType(Me.Resources("HideClockFaceStoryboard"), Storyboard)
            showWeatherImageStoryboard = CType(Me.Resources("ShowWeatherImageStoryboard"), Storyboard)
            hideWeatherImageStoryboard = CType(Me.Resources("HideWeatherImageStoryboard"), Storyboard)
        End Sub

        Private Sub ForecastButtonHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Change the status image and start the rotation animation.
            fetchButton.IsEnabled = False
            fetchButton.Content = "Contacting Server"
            weatherText.Text = ""
            hideWeatherImageStoryboard.Begin(Me)

            ' Start fetching the weather forecast asynchronously.
            Dim fetcher As New NoArgDelegate(AddressOf Me.FetchWeatherFromServer)

            fetcher.BeginInvoke(Nothing, Nothing)
        End Sub

        Private Sub FetchWeatherFromServer()
            ' Simulate the delay from network access.
            Thread.Sleep(4000)

            ' Tried and true method for weather forecasting - random numbers.
            Dim rand As New Random()
            Dim weather As String

            If rand.Next(2) = 0 Then
                weather = "rainy"
            Else
                weather = "sunny"
            End If

            ' Schedule the update function in the UI thread.
            tomorrowsWeather.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, New OneArgDelegate(AddressOf UpdateUserInterface), weather)
        End Sub

        Private Sub UpdateUserInterface(ByVal weather As String)
            'Set the weather image
            If weather = "sunny" Then
                weatherIndicatorImage.Source = CType(Me.Resources("SunnyImageSource"), ImageSource)
            ElseIf weather = "rainy" Then
                weatherIndicatorImage.Source = CType(Me.Resources("RainingImageSource"), ImageSource)
            End If

            'Stop clock animation
            showClockFaceStoryboard.Stop(Me)
            hideClockFaceStoryboard.Begin(Me)

            'Update UI text
            fetchButton.IsEnabled = True
            fetchButton.Content = "Fetch Forecast"
            weatherText.Text = weather
        End Sub

        Private Sub HideClockFaceStoryboard_Completed(ByVal sender As Object, ByVal args As EventArgs)
            showWeatherImageStoryboard.Begin(Me)
        End Sub

        Private Sub HideWeatherImageStoryboard_Completed(ByVal sender As Object, ByVal args As EventArgs)
            showClockFaceStoryboard.Begin(Me, True)
        End Sub
    End Class
End Namespace

Im Folgenden sind einige der Details aufgeführt, die beachtet werden sollten.

  • Erstellen des Schaltflächenhandlers

    private void ForecastButtonHandler(object sender, RoutedEventArgs e)
    {
        // Change the status image and start the rotation animation.
        fetchButton.IsEnabled = false;
        fetchButton.Content = "Contacting Server";
        weatherText.Text = "";
        hideWeatherImageStoryboard.Begin(this);
    
        // Start fetching the weather forecast asynchronously.
        NoArgDelegate fetcher = new NoArgDelegate(
            this.FetchWeatherFromServer);
    
        fetcher.BeginInvoke(null, null);
    }
    
    Private Sub ForecastButtonHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
        ' Change the status image and start the rotation animation.
        fetchButton.IsEnabled = False
        fetchButton.Content = "Contacting Server"
        weatherText.Text = ""
        hideWeatherImageStoryboard.Begin(Me)
    
        ' Start fetching the weather forecast asynchronously.
        Dim fetcher As New NoArgDelegate(AddressOf Me.FetchWeatherFromServer)
    
        fetcher.BeginInvoke(Nothing, Nothing)
    End Sub
    

Wenn auf die Schaltfläche geklickt wird, wird die Uhr-Zeichnung angezeigt, und wir beginnen mit der Animation. Die Schaltfläche wird deaktiviert. Wir rufen die FetchWeatherFromServer -Methode in einem neuen Thread auf und geben dann zurück, sodass Dispatcher Ereignisse verarbeitet werden können, während wir auf die Wettervorhersage warten.

  • Abrufen der Wettervorhersage

    private void FetchWeatherFromServer()
    {
        // Simulate the delay from network access.
        Thread.Sleep(4000);
    
        // Tried and true method for weather forecasting - random numbers.
        Random rand = new Random();
        String weather;
    
        if (rand.Next(2) == 0)
        {
            weather = "rainy";
        }
        else
        {
            weather = "sunny";
        }
    
        // Schedule the update function in the UI thread.
        tomorrowsWeather.Dispatcher.BeginInvoke(
            System.Windows.Threading.DispatcherPriority.Normal,
            new OneArgDelegate(UpdateUserInterface),
            weather);
    }
    
    Private Sub FetchWeatherFromServer()
        ' Simulate the delay from network access.
        Thread.Sleep(4000)
    
        ' Tried and true method for weather forecasting - random numbers.
        Dim rand As New Random()
        Dim weather As String
    
        If rand.Next(2) = 0 Then
            weather = "rainy"
        Else
            weather = "sunny"
        End If
    
        ' Schedule the update function in the UI thread.
        tomorrowsWeather.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, New OneArgDelegate(AddressOf UpdateUserInterface), weather)
    End Sub
    

Aus Gründen der Einfachheit verwenden wir in diesem Beispiel keinen Netzwerk-Code. Stattdessen simulieren wir die Verzögerung des Netzwerkzugriffs, indem wir den neuen Thread für vier Sekunden in den Ruhezustand versetzen. In diesem Zeitraum wird der ursprüngliche Benutzeroberfläche Thread weiterhin ausgeführt und antwortet auf Ereignisse. Um dies zu zeigen, führen wir eine Animation weiterhin aus, und die Schaltflächen „Minimieren“ und „Maximieren“ funktionieren ebenfalls weiterhin.

Wenn die Verzögerung abgeschlossen ist und wir nach dem Zufallsprinzip unsere Wettervorhersage ausgewählt haben, ist es an der Zeit, an den Thread zurückzukehren Benutzeroberfläche . Dies geschieht durch Planen eines UpdateUserInterface Benutzeroberfläche Aufrufes im Thread mithilfe der des Threads Dispatcher . Wir übergeben eine Zeichenfolge, die das Wetter beschreibt, an diesen eingeplanten Methodenaufruf.

  • Aktualisieren der Benutzeroberfläche

    private void UpdateUserInterface(String weather)
    {
        //Set the weather image
        if (weather == "sunny")
        {
            weatherIndicatorImage.Source = (ImageSource)this.Resources[
                "SunnyImageSource"];
        }
        else if (weather == "rainy")
        {
            weatherIndicatorImage.Source = (ImageSource)this.Resources[
                "RainingImageSource"];
        }
    
        //Stop clock animation
        showClockFaceStoryboard.Stop(this);
        hideClockFaceStoryboard.Begin(this);
    
        //Update UI text
        fetchButton.IsEnabled = true;
        fetchButton.Content = "Fetch Forecast";
        weatherText.Text = weather;
    }
    
    Private Sub UpdateUserInterface(ByVal weather As String)
        'Set the weather image
        If weather = "sunny" Then
            weatherIndicatorImage.Source = CType(Me.Resources("SunnyImageSource"), ImageSource)
        ElseIf weather = "rainy" Then
            weatherIndicatorImage.Source = CType(Me.Resources("RainingImageSource"), ImageSource)
        End If
    
        'Stop clock animation
        showClockFaceStoryboard.Stop(Me)
        hideClockFaceStoryboard.Begin(Me)
    
        'Update UI text
        fetchButton.IsEnabled = True
        fetchButton.Content = "Fetch Forecast"
        weatherText.Text = weather
    End Sub
    

Wenn der Dispatcher im Benutzeroberfläche Thread Zeit hat, führt er den geplanten Aufruf von aus UpdateUserInterface . Diese Methode hält die Uhr-Animation an und wählt ein Bild aus, um das Wetter zu beschreiben. Sie zeigt dieses Bild an und stellt die Schaltfläche „fetch forecast“ (Wettervorhersage abrufen) wieder her.

Mehrere Fenster, mehrere Threads

Einige WPF Anwendungen erfordern mehrere Fenster der obersten Ebene. Es ist durchaus akzeptabel, dass ein Thread/eine Dispatcher Kombination mehrere Fenster verwaltet, aber manchmal haben mehrere Threads einen besseren Job. Dies trifft besonders zu, wenn die Möglichkeit besteht, dass eines der Fenster den Thread für sich beansprucht.

Windows-Explorer funktioniert auf diese Weise. Jedes neue Explorer-Fenster gehört zum ursprünglichen Prozess, wird jedoch unter der Kontrolle eines unabhängigen Threads erstellt.

Mithilfe eines- WPF Frame Steuer Elements können wir Webseiten anzeigen. Wir können problemlos einen einfachen Ersatz für Internet Explorer erstellen. Wir beginnen mit einer wichtigen Funktion, und zwar der Möglichkeit, ein neues Explorerfenster zu öffnen. Wenn der Benutzer auf die Schaltfläche „Neues Fenster“ klickt, starten wir eine Kopie des Fensters in einem separaten Thread. Auf diese Weise sperren lange andauernde oder blockierende Vorgänge in einem der Fenster nicht alle anderen Fenster.

In Wirklichkeit verfügt das Webbrowser-Modell über ein eigenes kompliziertes Threadingmodell. Wir haben uns dafür entschieden, da die meisten Leser damit vertraut sein sollten.

Im folgenden Beispiel wird der Code angezeigt.

<Window x:Class="SDKSamples.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MultiBrowse"
    Height="600" 
    Width="800"
    Loaded="OnLoaded"
    >
  <StackPanel Name="Stack" Orientation="Vertical">
    <StackPanel Orientation="Horizontal">
      <Button Content="New Window"
              Click="NewWindowHandler" />
      <TextBox Name="newLocation"
               Width="500" />
      <Button Content="GO!"
              Click="Browse" />
    </StackPanel>

    <Frame Name="placeHolder"
            Width="800"
            Height="550"></Frame>
  </StackPanel>
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Threading;
using System.Threading;

namespace SDKSamples
{
    public partial class Window1 : Window
    {

        public Window1() : base()
        {
            InitializeComponent();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
           placeHolder.Source = new Uri("http://www.msn.com");
        }

        private void Browse(object sender, RoutedEventArgs e)
        {
            placeHolder.Source = new Uri(newLocation.Text);
        }

        private void NewWindowHandler(object sender, RoutedEventArgs e)
        {
            Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
            newWindowThread.SetApartmentState(ApartmentState.STA);
            newWindowThread.IsBackground = true;
            newWindowThread.Start();
        }

        private void ThreadStartingPoint()
        {
            Window1 tempWindow = new Window1();
            tempWindow.Show();
            System.Windows.Threading.Dispatcher.Run();
        }
    }
}

Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Threading
Imports System.Threading


Namespace SDKSamples
    Partial Public Class Window1
        Inherits Window

        Public Sub New()
            MyBase.New()
            InitializeComponent()
        End Sub

        Private Sub OnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
           placeHolder.Source = New Uri("http://www.msn.com")
        End Sub

        Private Sub Browse(ByVal sender As Object, ByVal e As RoutedEventArgs)
            placeHolder.Source = New Uri(newLocation.Text)
        End Sub

        Private Sub NewWindowHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim newWindowThread As New Thread(New ThreadStart(AddressOf ThreadStartingPoint))
            newWindowThread.SetApartmentState(ApartmentState.STA)
            newWindowThread.IsBackground = True
            newWindowThread.Start()
        End Sub

        Private Sub ThreadStartingPoint()
            Dim tempWindow As New Window1()
            tempWindow.Show()
            System.Windows.Threading.Dispatcher.Run()
        End Sub
    End Class
End Namespace

Die folgenden Threadingsegmente dieses Codes sind für uns in diesem Kontext am interessantesten:

private void NewWindowHandler(object sender, RoutedEventArgs e)
{
    Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
    newWindowThread.SetApartmentState(ApartmentState.STA);
    newWindowThread.IsBackground = true;
    newWindowThread.Start();
}
Private Sub NewWindowHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim newWindowThread As New Thread(New ThreadStart(AddressOf ThreadStartingPoint))
    newWindowThread.SetApartmentState(ApartmentState.STA)
    newWindowThread.IsBackground = True
    newWindowThread.Start()
End Sub

Diese Methode wird aufgerufen, wenn die Schaltfläche „Neues Fenster“ angeklickt wird. Sie erstellt einen neuen Thread und startet diesen asynchron.

private void ThreadStartingPoint()
{
    Window1 tempWindow = new Window1();
    tempWindow.Show();
    System.Windows.Threading.Dispatcher.Run();
}
Private Sub ThreadStartingPoint()
    Dim tempWindow As New Window1()
    tempWindow.Show()
    System.Windows.Threading.Dispatcher.Run()
End Sub

Diese Methode ist der Ausgangspunkt für den neuen Thread. Wir erstellen ein neues Fenster unter der Kontrolle dieses Threads. WPF erstellt automatisch einen neuen Dispatcher , um den neuen Thread zu verwalten. Wir müssen lediglich den starten, um das Fenster funktionsfähig zu machen Dispatcher .

Technische Details und Stolpersteine

Schreiben von Komponenten mithilfe von Threading

Das Entwicklerhandbuch für Microsoft .NET Framework beschreibt ein Muster für die Bereitstellung von asynchronem Verhalten durch eine Komponente für die Clients (siehe Übersicht über das ereignisbasierte asynchrone Muster). Nehmen wir beispielsweise an, dass die FetchWeatherFromServer Methode in eine wiederverwendbare, nicht grafische Komponente integriert werden soll. Nach dem Standard-Microsoft .NET Framework-Muster würde dies in etwa wie folgt aussehen.

public class WeatherComponent : Component
{
    //gets weather: Synchronous
    public string GetWeather()
    {
        string weather = "";

        //predict the weather

        return weather;
    }

    //get weather: Asynchronous
    public void GetWeatherAsync()
    {
        //get the weather
    }

    public event GetWeatherCompletedEventHandler GetWeatherCompleted;
}

public class GetWeatherCompletedEventArgs : AsyncCompletedEventArgs
{
    public GetWeatherCompletedEventArgs(Exception error, bool canceled,
        object userState, string weather)
        :
        base(error, canceled, userState)
    {
        _weather = weather;
    }

    public string Weather
    {
        get { return _weather; }
    }
    private string _weather;
}

public delegate void GetWeatherCompletedEventHandler(object sender,
    GetWeatherCompletedEventArgs e);
Public Class WeatherComponent
    Inherits Component
    'gets weather: Synchronous 
    Public Function GetWeather() As String
        Dim weather As String = ""

        'predict the weather

        Return weather
    End Function

    'get weather: Asynchronous 
    Public Sub GetWeatherAsync()
        'get the weather
    End Sub

    Public Event GetWeatherCompleted As GetWeatherCompletedEventHandler
End Class

Public Class GetWeatherCompletedEventArgs
    Inherits AsyncCompletedEventArgs
    Public Sub New(ByVal [error] As Exception, ByVal canceled As Boolean, ByVal userState As Object, ByVal weather As String)
        MyBase.New([error], canceled, userState)
        _weather = weather
    End Sub

    Public ReadOnly Property Weather() As String
        Get
            Return _weather
        End Get
    End Property
    Private _weather As String
End Class

Public Delegate Sub GetWeatherCompletedEventHandler(ByVal sender As Object, ByVal e As GetWeatherCompletedEventArgs)

GetWeatherAsync würde eine der zuvor beschriebenen Techniken wie z.B. das Erstellen eines Hintergrundthreads verwenden, um die Arbeit asynchron auszuführen und den aufrufenden Thread nicht zu blockieren.

Einer der wichtigsten Teile dieses Musters ist das Aufrufen der MethodName - Completed Methode für denselben Thread, der die MethodName -Methode aufgerufen hat Async , um mit zu beginnen. Dies können Sie WPF recht einfach tun, indem Sie – speichern, CurrentDispatcher aber die nicht grafische Komponente kann nur in Anwendungen verwendet werden WPF , nicht in Windows Forms-oder ASP.NET-Programmen.

Die- DispatcherSynchronizationContext Klasse erfüllt diese Anforderung – stellen Sie sich diese als vereinfachte Version von Dispatcher vor, die auch mit anderen Frameworks funktioniert Benutzeroberfläche .

public class WeatherComponent2 : Component
{
    public string GetWeather()
    {
        return fetchWeatherFromServer();
    }

    private DispatcherSynchronizationContext requestingContext = null;

    public void GetWeatherAsync()
    {
        if (requestingContext != null)
            throw new InvalidOperationException("This component can only handle 1 async request at a time");

        requestingContext = (DispatcherSynchronizationContext)DispatcherSynchronizationContext.Current;

        NoArgDelegate fetcher = new NoArgDelegate(this.fetchWeatherFromServer);

        // Launch thread
        fetcher.BeginInvoke(null, null);
    }

    private void RaiseEvent(GetWeatherCompletedEventArgs e)
    {
        if (GetWeatherCompleted != null)
            GetWeatherCompleted(this, e);
    }

    private string fetchWeatherFromServer()
    {
        // do stuff
        string weather = "";

        GetWeatherCompletedEventArgs e =
            new GetWeatherCompletedEventArgs(null, false, null, weather);

        SendOrPostCallback callback = new SendOrPostCallback(DoEvent);
        requestingContext.Post(callback, e);
        requestingContext = null;

        return e.Weather;
    }

    private void DoEvent(object e)
    {
        //do stuff
    }

    public event GetWeatherCompletedEventHandler GetWeatherCompleted;
    public delegate string NoArgDelegate();
}
Public Class WeatherComponent2
    Inherits Component
    Public Function GetWeather() As String
        Return fetchWeatherFromServer()
    End Function

    Private requestingContext As DispatcherSynchronizationContext = Nothing

    Public Sub GetWeatherAsync()
        If requestingContext IsNot Nothing Then
            Throw New InvalidOperationException("This component can only handle 1 async request at a time")
        End If

        requestingContext = CType(DispatcherSynchronizationContext.Current, DispatcherSynchronizationContext)

        Dim fetcher As New NoArgDelegate(AddressOf Me.fetchWeatherFromServer)

        ' Launch thread
        fetcher.BeginInvoke(Nothing, Nothing)
    End Sub

    Private Sub [RaiseEvent](ByVal e As GetWeatherCompletedEventArgs)
        RaiseEvent GetWeatherCompleted(Me, e)
    End Sub

    Private Function fetchWeatherFromServer() As String
        ' do stuff
        Dim weather As String = ""

        Dim e As New GetWeatherCompletedEventArgs(Nothing, False, Nothing, weather)

        Dim callback As New SendOrPostCallback(AddressOf DoEvent)
        requestingContext.Post(callback, e)
        requestingContext = Nothing

        Return e.Weather
    End Function

    Private Sub DoEvent(ByVal e As Object)
        'do stuff
    End Sub

    Public Event GetWeatherCompleted As GetWeatherCompletedEventHandler
    Public Delegate Function NoArgDelegate() As String
End Class

Geschachtelte Verteilung

Manchmal ist es nicht möglich, den Thread vollständig zu sperren Benutzeroberfläche . Betrachten wir die- Show Methode der- MessageBox Klasse. Show gibt erst zurück, wenn der Benutzer auf die Schaltfläche OK klickt. Es wird jedoch ein Fenster erstellt, das über eine Meldungsschleife verfügen muss, um interaktiv zu sein. Während wir warten, bis der Benutzer auf „OK“ klickt, reagiert das ursprüngliche Anwendungsfenster nicht auf Benutzereingaben. Es verarbeitet jedoch weiterhin Paint-Meldungen. Das ursprüngliche Fenster zeichnet sich selbst neu, wenn es verdeckt war und dann wieder angezeigt wird.

Screenshot, der eine MessageBox mit der Schaltfläche "OK" anzeigt

Ein Thread muss für das Meldungsfenster zuständig sein. WPF könnte einen neuen Thread nur für das Meldungsfenster erstellen, aber dieser Thread wäre nicht in der Lage, die deaktivierten Elemente im ursprünglichen Fenster zu zeichnen (beachten Sie den bereits erwähnten gegenseitigen Ausschluss). Stattdessen WPF verwendet ein System eigenes Nachrichten Verarbeitungssystem. Die Dispatcher -Klasse enthält eine spezielle Methode namens PushFrame , die den aktuellen Ausführungs Punkt einer Anwendung speichert und dann eine neue Nachrichten Schleife startet. Wenn die schsted Message-Schleife abgeschlossen ist, wird die Ausführung nach dem ursprünglichen-Befehl fortgesetzt PushFrame .

In diesem Fall PushFrame behält den Programmkontext beim-Aufrufvorgang bei MessageBox.Show und startet eine neue Nachrichten Schleife, um das Hintergrund Fenster neu zu zeichnen und Eingaben in das Meldungs Feld Fenster zu behandeln. Wenn der Benutzer auf OK klickt und das Popup Fenster löscht, wird die schsted-Schleife beendet, und die Steuerung wird nach dem Aufrufen von fortgesetzt Show .

Veraltete Routingereignisse

Das Routing Ereignis System in WPF benachrichtigt gesamte Strukturen, wenn Ereignisse ausgelöst werden.

<Canvas MouseLeftButtonDown="handler1" 
        Width="100"
        Height="100"
        >
  <Ellipse Width="50"
           Height="50"
           Fill="Blue" 
           Canvas.Left="30"
           Canvas.Top="50" 
           MouseLeftButtonDown="handler2"
           />
</Canvas>

Wenn die linke Maustaste über der Ellipse gedrückt wird, handler2 wird ausgeführt. Nachdem handler2 abgeschlossen wurde, wird das-Ereignis an das-Objekt weitergegeben Canvas , das handler1 von zur Verarbeitung verwendet wird. Dies geschieht nur, wenn handler2 das Ereignis Objekt nicht explizit als behandelt kennzeichnet.

Es ist möglich, dass die handler2 Verarbeitung dieses Ereignisses sehr viel Zeit in Anspruch nimmt. handler2 verwendet möglicherweise PushFrame , um eine schinstanzschleife zu starten, die für Stunden nicht zurückgibt. Wenn handler2 das Ereignis nicht als behandelt markiert, wenn diese Nachrichten Schleife beendet ist, wird das Ereignis in der Struktur nach oben weitergegeben, obwohl es sehr alt ist.

Wiedereintritt und Sperrung

Der Sperrmechanismus des Common Language Runtime (CLR) verhält sich nicht genau so, wie man sich vorstellen könnte. ein Thread kann davon ausgehen, dass der Vorgang vollständig beendet wird, wenn eine Sperre angefordert wird. In Wirklichkeit empfängt und verarbeitet der Thread weiterhin Meldungen mit hoher Priorität. Dadurch können Deadlocks vermieden und Schnittstellen minimal reaktionsfähig gemacht werden, aber dadurch können auch schwer erkennbare Fehler entstehen. Der Großteil der Fälle, in denen Sie nichts wissen müssen, aber in seltenen Fällen (in der Regel mit Win32-Fenster Meldungen oder COM-STA-Komponenten) ist dies zu wissen.

Die meisten Schnittstellen werden nicht mit Thread Sicherheit erstellt, da Entwickler unter der Annahme arbeiten, dass Benutzeroberfläche nie von mehr als einem Thread darauf zugegriffen wird. In diesem Fall kann es vorkommen, dass ein einzelner Thread Änderungen an der Umgebung zu unerwarteten Zeiten durchführt, wodurch die negativen Auswirkungen, die der DispatcherObject gegenseitige Ausschlussmechanismus lösen soll, entstehen. Betrachten Sie den folgenden Pseudocode:

Das Diagramm, das das wieder eintreten des Threading anzeigt.

In den meisten Fällen ist dies die richtige Sache, aber es gibt Zeiten, in WPF denen eine solche unerwartete Wiederaufnahme tatsächlich Probleme verursachen kann. Daher wird zu bestimmten Zeitpunkten WPF aufgerufen DisableProcessing , wodurch die Lock-Anweisung für diesen Thread so geändert wird, dass die WPF Wiedereintritts freie Sperre anstelle der üblichen CLR-Sperre verwendet wird.

Warum hat das CLR-Team dieses Verhalten gewählt? Dies hatte mit COM-STA-Objekten und dem Finalizerthread zu tun. Wenn für ein Objekt eine Garbage Collection durchgeführt wird, wird die zugehörige- Finalize Methode auf dem dedizierten Finalizerthread ausgeführt, nicht auf dem Benutzeroberfläche Thread. Und darin liegt das Problem, da ein COM-STA-Objekt, das auf dem Thread erstellt wurde, Benutzeroberfläche nur im Thread verworfen werden kann Benutzeroberfläche . Die CLR übernimmt das Äquivalent eines BeginInvoke (in diesem Fall mit Win32's SendMessage ). Wenn der Benutzeroberfläche Thread jedoch ausgelastet ist, wird der Finalizerthread angehalten, und das COM-STA-Objekt kann nicht verworfen werden. dadurch entsteht ein schwerwiegender Speicherplatz. Das CLR-Team hat also den harten Aufruf getroffen, damit Sperren so funktionieren, wie Sie funktionieren.

Die Aufgabe für besteht darin, unerwartete Eintritts Übereinstimmung WPF zu vermeiden, ohne den Speicherplatz zu wiederholen. aus diesem Grund blockieren wir den erneuten eintreten nicht überall.

Weitere Informationen