Threading-ModellThreading Model

Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) soll Entwicklern bei Problemen mit Threading helfen.is designed to save developers from the difficulties of threading. Folglich müssen die meisten WPFWPF Entwickler keine Schnittstelle schreiben, die mehr als einen Thread verwendet.As a result, the majority of WPFWPF developers won't have to write an interface that uses more than one thread. Da Multithreadprogramme komplex und schwierig zu debuggen sind, sollten sie vermieden werden, wenn Singlethread-Lösungen vorhanden sind.Because multithreaded programs are complex and difficult to debug, they should be avoided when single-threaded solutions exist.

Unabhängig davon, wie gut entworfen wurde, kann jedoch kein UIUI Framework eine Single Thread Lösung für jede Art von Problem bereitstellen.No matter how well architected, however, no UIUI framework will ever be able to provide a single-threaded solution for every sort of problem. WPFWPF kommt nahe, aber es gibt immer noch Situationen, in denen mehrere Threads Benutzeroberfläche (User Interface, UI)user interface (UI) Reaktionsfähigkeit oder Anwendungsleistung verbessern.comes close, but there are still situations where multiple threads improve Benutzeroberfläche (User Interface, UI)user interface (UI) responsiveness or application performance. In diesem Artikel werden zunächst einige Hintergrundinformationen angegeben, dann einige dieser Situationen beschrieben und am Ende einige Details auf niedriger Ebene besprochen.After discussing some background material, this paper explores some of these situations and then concludes with a discussion of some lower-level details.

Hinweis

In diesem Thema wird das Threading mithilfe der BeginInvoke-Methode für asynchrone Aufrufe erläutert.This topic discusses threading by using the BeginInvoke method for asynchronous calls. Sie können auch asynchrone Aufrufe durchführen, indem Sie die InvokeAsync-Methode aufrufen, die eine Action oder Func<TResult> als Parameter annimmt.You can also make asynchronous calls by calling the InvokeAsync method, which take an Action or Func<TResult> as a parameter. Die InvokeAsync-Methode gibt eine DispatcherOperation oder DispatcherOperation<TResult>zurück, die über eine Task-Eigenschaft verfügt.The InvokeAsync method returns a DispatcherOperation or DispatcherOperation<TResult>, which has a Task property. Sie können das await-Schlüsselwort entweder mit dem DispatcherOperation oder dem zugeordneten Taskverwenden.You can use the await keyword with either the DispatcherOperation or the associated 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.If you need to wait synchronously for the Task that is returned by a DispatcherOperation or DispatcherOperation<TResult>, call the DispatcherOperationWait extension method. Das Aufrufen von Task.Wait führt zu einem Deadlock.Calling Task.Wait will result in a deadlock. Weitere Informationen zum Verwenden einer Task zum Ausführen von asynchronen Vorgängen finden Sie Unteraufgaben Parallelität.For more information about using a Task to perform asynchronous operations, see Task Parallelism. Die Invoke-Methode verfügt auch über über Ladungen, die eine Action oder Func<TResult> als Parameter annehmen.The Invoke method also has overloads that take an Action or Func<TResult> as a parameter. Mithilfe der Invoke-Methode können Sie synchrone Aufrufe durchführen, indem Sie einen Delegaten, Action oder Func<TResult>übergeben.You can use the Invoke method to make synchronous calls by passing in a delegate, Action or Func<TResult>.

Übersicht und VerteilerOverview and the Dispatcher

In der Regel beginnen WPFWPF Anwendungen mit zwei Threads: einer für die Behandlung von Rendering und einem weiteren zum Verwalten des UIUI.Typically, WPFWPF applications start with two threads: one for handling rendering and another for managing the UIUI. Der Renderingthread wird im Hintergrund ausgeblendet ausgeführt, während der UIUI Thread Eingaben empfängt, Ereignisse behandelt, den Bildschirm zeichnet und Anwendungscode ausführt.The rendering thread effectively runs hidden in the background while the UIUI thread receives input, handles events, paints the screen, and runs application code. Die meisten Anwendungen verwenden einen einzelnen UIUI Thread, obwohl es in manchen Situationen am besten ist, mehrere zu verwenden.Most applications use a single UIUI thread, although in some situations it is best to use several. Dies wird weiter unten mit einem Beispiel erläutert.We’ll discuss this with an example later.

Der UIUI Thread fügt Arbeitselemente in ein Objekt ein, das als Dispatcherbezeichnet wird.The UIUI thread queues work items inside an object called a Dispatcher. Vom Dispatcher werden Arbeitsaufgaben nach Priorität ausgewählt und jeweils vollständig ausgeführt.The Dispatcher selects work items on a priority basis and runs each one to completion. Jeder UIUI Thread muss mindestens ein Dispatcherhaben, und jeder Dispatcher kann Arbeitsaufgaben in genau einem Thread ausführen.Every UIUI thread must have at least one Dispatcher, and each Dispatcher can execute work items in exactly one thread.

Der Trick, reaktionsfähige, benutzerfreundliche Anwendungen zu entwickeln, besteht darin, den Dispatcher Durchsatz zu maximieren, indem die Arbeitselemente gering gehalten werden.The trick to building responsive, user-friendly applications is to maximize the Dispatcher throughput by keeping the work items small. Auf diese Weise werden Elemente in der Dispatcher Warteschlange, die auf die Verarbeitung wartet, nie abgelaufen.This way items never get stale sitting in the Dispatcher queue waiting for processing. Jede spürbare Verzögerung zwischen Eingabe und Antwort kann für einen Benutzer frustrierend sein.Any perceivable delay between input and response can frustrate a user.

Wie werden dann WPFWPF Anwendungen mit großen Vorgängen arbeiten sollten?How then are WPFWPF applications supposed to handle big operations? Was geschieht, wenn der Code eine aufwändige Berechnung beinhaltet oder eine Abfrage einer Datenbank auf einem Remoteserver ausführen muss?What if your code involves a large calculation or needs to query a database on some remote server? In der Regel besteht die Antwort darin, den großen Vorgang in einem separaten Thread zu behandeln, sodass der UIUI Thread frei ist, um Elemente in der Dispatcher Warteschlange zu neigen.Usually, the answer is to handle the big operation in a separate thread, leaving the UIUI thread free to tend to items in the Dispatcher queue. Wenn der große Vorgang fertiggestellt ist, kann er sein Ergebnis zurück an den UIUI Thread zur Anzeige melden.When the big operation is complete, it can report its result back to the UIUI thread for display.

In der Vergangenheit ermöglicht Windows den Zugriff auf UIUI Elemente nur durch den Thread, der Sie erstellt hat.Historically, Windows allows UIUI elements to be accessed only by the thread that created them. Dies bedeutet, dass ein Hintergrundthread bei einer Aufgabe mit langer Laufzeit kein Textfeld aktualisieren kann, wenn er abgeschlossen ist.This means that a background thread in charge of some long-running task cannot update a text box when it is finished. Windows führt dies aus, um die Integrität der UIUI Komponenten sicherzustellen.Windows does this to ensure the integrity of UIUI components. Ein Listenfeld könnte merkwürdig aussehen, wenn sein Inhalt während des Zeichnens von einem Hintergrundthread aktualisiert werden würde.A list box could look strange if its contents were updated by a background thread during painting.

WPFWPF verfügt über einen integrierten gegenseitigen Ausschlussmechanismus, der diese Koordination erzwingt.has a built-in mutual exclusion mechanism that enforces this coordination. Die meisten Klassen in WPFWPF von DispatcherObjectabgeleitet.Most classes in WPFWPF derive from DispatcherObject. Bei der Erstellung speichert ein DispatcherObject einen Verweis auf die Dispatcher, die mit dem derzeit laufenden Thread verknüpft ist.At construction, a DispatcherObject stores a reference to the Dispatcher linked to the currently running thread. Tatsächlich wird die DispatcherObject dem Thread zugeordnet, der Sie erstellt.In effect, the DispatcherObject associates with the thread that creates it. Während der Programmausführung kann ein DispatcherObject seine öffentliche VerifyAccess-Methode aufzurufen.During program execution, a DispatcherObject can call its public VerifyAccess method. VerifyAccess überprüft die Dispatcher, die dem aktuellen Thread zugeordnet sind, und vergleicht sie mit dem Dispatcher Verweis, der während der Erstellung gespeichert wird.VerifyAccess examines the Dispatcher associated with the current thread and compares it to the Dispatcher reference stored during construction. Wenn keine Entsprechung gefunden wird, löst VerifyAccess eine Ausnahme aus.If they don’t match, VerifyAccess throws an exception. VerifyAccess soll am Anfang jeder Methode aufgerufen werden, die zu einer DispatcherObjectgehört.VerifyAccess is intended to be called at the beginning of every method belonging to a DispatcherObject.

Wie interagieren Hintergrundthreads mit dem Benutzer, wenn nur ein Thread die UIUIändern kann?If only one thread can modify the UIUI, how do background threads interact with the user? Ein Hintergrund Thread kann den UIUI Thread bitten, einen Vorgang in seinem Auftrag auszuführen.A background thread can ask the UIUI thread to perform an operation on its behalf. Dies erfolgt durch das Registrieren eines Arbeits Elements mit dem Dispatcher des UIUI Threads.It does this by registering a work item with the Dispatcher of the UIUI thread. Die Dispatcher-Klasse stellt zwei Methoden zum Registrieren von Arbeits Elementen bereit: Invoke und BeginInvoke.The Dispatcher class provides two methods for registering work items: Invoke and BeginInvoke. Beide Methoden planen einen Delegaten für die Ausführung ein.Both methods schedule a delegate for execution. Invoke ist ein synchroner Aufruf – das heißt, er wird erst zurückgegeben, wenn der UIUI Thread die Ausführung des Delegaten tatsächlich abgeschlossen hat.Invoke is a synchronous call – that is, it doesn’t return until the UIUI thread actually finishes executing the delegate. BeginInvoke ist asynchron und wird sofort zurückgegeben.BeginInvoke is asynchronous and returns immediately.

Der Dispatcher ordnet die Elemente in der Warteschlange nach Priorität an.The Dispatcher orders the elements in its queue by priority. Es gibt zehn Ebenen, die beim Hinzufügen eines Elements zur Dispatcher Warteschlange angegeben werden können.There are ten levels that may be specified when adding an element to the Dispatcher queue. Diese Prioritäten werden in der DispatcherPriority-Enumeration beibehalten.These priorities are maintained in the DispatcherPriority enumeration. Ausführliche Informationen zu DispatcherPriority Ebenen finden Sie in der Windows SDK-Dokumentation.Detailed information about DispatcherPriority levels can be found in the Windows SDK documentation.

Threads in Aktion: BeispieleThreads in Action: The Samples

Eine Singlethread-Anwendung mit einer Berechnung mit langer LaufzeitA Single-Threaded Application with a Long-Running Calculation

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.Most graphical user interfaces (GUIs) spend a large portion of their time idle while waiting for events that are generated in response to user interactions. Bei sorgfältiger Programmierung kann diese Leerlaufzeit konstruktiv genutzt werden, ohne dass sich dies auf die Reaktionsfähigkeit der UIUIauswirkt.With careful programming this idle time can be used constructively, without affecting the responsiveness of the UIUI. Das WPFWPF Threading Modell lässt nicht zu, dass Eingaben einen Vorgang im UIUI Thread unterbrechen.The WPFWPF threading model doesn’t allow input to interrupt an operation happening in the UIUI thread. Dies bedeutet, dass Sie in regelmäßigen Abständen zum Dispatcher zurückkehren müssen, um ausstehende Eingabeereignisse zu verarbeiten, bevor Sie veraltet sind.This means you must be sure to return to the Dispatcher periodically to process pending input events before they get stale.

Betrachten Sie das folgende Beispiel:Consider the following example:

Screenshot, der das Threading von Primzahlen zeigt.

Diese einfache Anwendung zählt ab drei aufwärts und sucht dabei nach Primzahlen.This simple application counts upwards from three, searching for prime numbers. Wenn der Benutzer auf die Schaltfläche " Start " klickt, beginnt die Suche.When the user clicks the Start button, the search begins. Wenn das Programm eine Primzahl findet, wird die Benutzeroberfläche mit dieser Entdeckung aktualisiert.When the program finds a prime, it updates the user interface with its discovery. Der Benutzer kann die Suche zu jedem Zeitpunkt beenden.At any point, the user can stop the search.

Obwohl es sich um eine einfache Anwendung handelt, könnte die Suche nach Primzahlen endlos fortgesetzt werden, was einige Probleme bereitet.Although simple enough, the prime number search could go on forever, which presents some difficulties. Wenn wir die gesamte Suche innerhalb des Click-Ereignis Handlers der Schaltfläche behandelt haben, würden wir dem UIUI Thread nie die Möglichkeit einräumen, andere Ereignisse zu verarbeiten.If we handled the entire search inside of the click event handler of the button, we would never give the UIUI thread a chance to handle other events. Der UIUI kann nicht auf Eingabe-oder Verarbeitungs Nachrichten reagieren.The UIUI would be unable to respond to input or process messages. Sie würde nie neu zeichnen und nie auf Mausklicks auf die Schaltflächen reagieren.It would never repaint and never respond to button clicks.

Wir könnten die Suche nach Primzahlen in einem separaten Thread ausführen, aber dann hätten wir Synchronisierungsprobleme.We could conduct the prime number search in a separate thread, but then we would need to deal with synchronization issues. Mit einem Singlethread-Ansatz können wir die Bezeichnung, die die größte gefundene Primzahl auflistet, direkt aktualisieren.With a single-threaded approach, we can directly update the label that lists the largest prime found.

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.If we break up the task of calculation into manageable chunks, we can periodically return to the Dispatcher and process events. Wir können WPFWPF die Möglichkeit geben, Eingaben neu zu zeichnen und zu verarbeiten.We can give WPFWPF an opportunity to repaint and process input.

Die beste Möglichkeit, die Verarbeitungszeit zwischen Berechnung und Ereignis Behandlung aufzuteilen, besteht darin, die Berechnung aus der Dispatcherzu verwalten.The best way to split processing time between calculation and event handling is to manage calculation from the Dispatcher. Mit der BeginInvoke-Methode können wir Primzahlen Überprüfungen in derselben Warteschlange planen, von der UIUI Ereignisse gezeichnet werden.By using the BeginInvoke method, we can schedule prime number checks in the same queue that UIUI events are drawn from. In unserem Beispiel planen wir nur eine einzige Primzahlüberprüfung zu einem Zeitpunkt ein.In our example, we schedule only a single prime number check at a time. Nach Abschluss der Primzahlüberprüfung planen wir sofort die nächste Überprüfung ein.After the prime number check is complete, we schedule the next check immediately. Diese Überprüfung wird erst durchgeführt, nachdem ausstehende UIUI Ereignisse behandelt wurden.This check proceeds only after pending UIUI events have been handled.

Screenshot, der die Verteiler-Warteschlange anzeigt

Microsoft Word führt die Rechtschreibprüfung mit diesem Mechanismus durch.Microsoft Word accomplishes spell checking using this mechanism. Die Rechtschreibprüfung erfolgt im Hintergrund mithilfe der Leerlaufzeit des UIUI Threads.Spell checking is done in the background using the idle time of the UIUI thread. Sehen wir uns den Code an.Let's take a look at the code.

Das folgende Beispiel zeigt die XAML, die die Benutzeroberfläche erstellt.The following example shows the XAML that creates the user interface.

<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.The following example shows the code-behind.

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 die Button.The following example shows the event handler for the 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 der Buttonist dieser Handler für die Planung der ersten Primzahlen Prüfung zuständig, indem der Dispatcher Warteschlange ein Delegat hinzugefügt wird.Besides updating the text on the Button, this handler is responsible for scheduling the first prime number check by adding a delegate to the Dispatcher queue. Nachdem der Ereignishandler seine Arbeit abgeschlossen hat, wählt der Dispatcher diesen Delegaten für die Ausführung aus.Sometime after this event handler has completed its work, the Dispatcher will select this delegate for execution.

Wie bereits erwähnt, ist BeginInvoke der Dispatcher Member, der zum Planen eines Delegaten für die Ausführung verwendet wird.As we mentioned earlier, BeginInvoke is the Dispatcher member used to schedule a delegate for execution. In diesem Fall wählen wir die SystemIdle Priorität aus.In this case, we choose the SystemIdle priority. Der Dispatcher führt diesen Delegaten nur aus, wenn keine wichtigen Ereignisse zu verarbeiten sind.The Dispatcher will execute this delegate only when there are no important events to process. UIUI-Reaktionsfähigkeit ist wichtiger als die Zahlenüberprüfung.responsiveness is more important than number checking. Wir übergeben auch einen neuen Delegaten, der die Zahlenüberprüfungsroutine darstellt.We also pass a new delegate representing the number-checking routine.

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.This method checks if the next odd number is prime. Wenn es sich um eine Primzahl handelt, aktualisiert die Methode die bigPrimeTextBlock direkt, um deren Ermittlung widerzuspiegeln.If it is prime, the method directly updates the bigPrimeTextBlock to reflect its discovery. Dies ist möglich, da die Berechnung im selben Thread ausgeführt wird, der für die Erstellung der Komponente verwendet wurde.We can do this because the calculation is occurring in the same thread that was used to create the component. 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 UIUI Thread ausführen.Had we chosen to use a separate thread for the calculation, we would have to use a more complicated synchronization mechanism and execute the update in the UIUI thread. Wir werden diese Situation im Folgenden zeigen.We’ll demonstrate this situation next.

Den gesamten Quellcode für dieses Beispiel finden Sie unter Beispiel für Single Thread-Anwendung mit langer Ausführungszeit .For the complete source code for this sample, see the Single-Threaded Application with Long-Running Calculation Sample

Behandeln eines blockierenden Vorgangs mit einem HintergrundthreadHandling a Blocking Operation with a Background Thread

Die Behandlung von blockierenden Vorgängen in einer grafischen Anwendung kann schwierig sein.Handling blocking operations in a graphical application can be difficult. Wir wollen keine blockierenden Methoden von Ereignishandlern aufrufen, da die Anwendung sonst scheinbar einfriert.We don’t want to call blocking methods from event handlers because the application will appear to freeze up. Wir können einen separaten Thread verwenden, um diese Vorgänge zu verarbeiten, aber wenn wir fertig sind, müssen wir mit dem UIUI Thread synchronisieren, da wir die GUI nicht direkt von unserem Arbeits Thread aus ändern können.We can use a separate thread to handle these operations, but when we’re done, we have to synchronize with the UIUI thread because we can’t directly modify the GUI from our worker thread. Wir können Invoke oder BeginInvoke verwenden, um Delegaten in die Dispatcher des UIUI Threads einzufügen.We can use Invoke or BeginInvoke to insert delegates into the Dispatcher of the UIUI thread. Diese Delegaten werden schließlich mit der Berechtigung zum Ändern UIUI Elemente ausgeführt.Eventually, these delegates will be executed with permission to modify UIUI elements.

In diesem Beispiel simulieren wir einen Remoteprozeduraufruf, der eine Wettervorhersage abruft.In this example, we mimic a remote procedure call that retrieves a weather forecast. Wir verwenden zum Ausführen dieses Aufrufes einen separaten Arbeits Thread, und wir planen eine Aktualisierungs Methode in der Dispatcher des UIUI Threads, wenn wir fertig sind.We use a separate worker thread to execute this call, and we schedule an update method in the Dispatcher of the UIUI thread when we’re finished.

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.The following are some of the details to be noted.

  • Erstellen des SchaltflächenhandlersCreating the Button Handler

    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.When the button is clicked, we display the clock drawing and start animating it. Die Schaltfläche wird deaktiviert.We disable the button. Wir rufen die FetchWeatherFromServer-Methode in einem neuen Thread auf und geben dann zurück, sodass der Dispatcher Ereignisse verarbeiten kann, während wir auf die Wettervorhersage warten.We invoke the FetchWeatherFromServer method in a new thread, and then we return, allowing the Dispatcher to process events while we wait to collect the weather forecast.

  • Abrufen der WettervorhersageFetching the Weather

    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.To keep things simple, we don’t actually have any networking code in this example. Stattdessen simulieren wir die Verzögerung des Netzwerkzugriffs, indem wir den neuen Thread für vier Sekunden in den Ruhezustand versetzen.Instead, we simulate the delay of network access by putting our new thread to sleep for four seconds. In diesem Zeitraum wird der ursprüngliche UIUI Thread weiterhin ausgeführt und antwortet auf Ereignisse.In this time, the original UIUI thread is still running and responding to events. Um dies zu zeigen, führen wir eine Animation weiterhin aus, und die Schaltflächen „Minimieren“ und „Maximieren“ funktionieren ebenfalls weiterhin.To show this, we’ve left an animation running, and the minimize and maximize buttons also continue to work.

Wenn die Verzögerung abgeschlossen ist und wir nach dem Zufallsprinzip unsere Wettervorhersage ausgewählt haben, ist es an der Zeit, an den UIUI Thread zurückzukehren.When the delay is finished, and we’ve randomly selected our weather forecast, it’s time to report back to the UIUI thread. Hierzu planen Sie einen UpdateUserInterface im UIUI Thread, der die Dispatcherdieses Threads verwendet.We do this by scheduling a call to UpdateUserInterface in the UIUI thread using that thread’s Dispatcher. Wir übergeben eine Zeichenfolge, die das Wetter beschreibt, an diesen eingeplanten Methodenaufruf.We pass a string describing the weather to this scheduled method call.

  • Aktualisieren der UIUIUpdating the UIUI

    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 die Dispatcher im UIUI Thread Zeit hat, wird der geplante Aufruf von UpdateUserInterfaceausgeführt.When the Dispatcher in the UIUI thread has time, it executes the scheduled call to UpdateUserInterface. Diese Methode hält die Uhr-Animation an und wählt ein Bild aus, um das Wetter zu beschreiben.This method stops the clock animation and chooses an image to describe the weather. Sie zeigt dieses Bild an und stellt die Schaltfläche „fetch forecast“ (Wettervorhersage abrufen) wieder her.It displays this image and restores the "fetch forecast" button.

Mehrere Fenster, mehrere ThreadsMultiple Windows, Multiple Threads

Einige WPFWPF Anwendungen erfordern mehrere Fenster der obersten Ebene.Some WPFWPF applications require multiple top-level windows. Es ist durchaus akzeptabel, dass eine Kombination aus Thread undDispatcher mehrere Fenster verwaltet, manchmal aber mehrere Threads einen besseren Auftrag ausführen.It is perfectly acceptable for one Thread/Dispatcher combination to manage multiple windows, but sometimes several threads do a better job. Dies trifft besonders zu, wenn die Möglichkeit besteht, dass eines der Fenster den Thread für sich beansprucht.This is especially true if there is any chance that one of the windows will monopolize the thread.

Windows-Explorer funktioniert auf diese Weise.Windows Explorer works in this fashion. Jedes neue Explorer-Fenster gehört zum ursprünglichen Prozess, wird jedoch unter der Kontrolle eines unabhängigen Threads erstellt.Each new Explorer window belongs to the original process, but it is created under the control of an independent thread.

Mithilfe eines WPFWPFFrame-Steuer Elements können wir Webseiten anzeigen.By using a WPFWPFFrame control, we can display Web pages. Wir können problemlos einen einfachen Ersatz für Internet Explorer erstellen.We can easily create a simple Internet Explorer substitute. Wir beginnen mit einer wichtigen Funktion, und zwar der Möglichkeit, ein neues Explorerfenster zu öffnen.We start with an important feature: the ability to open a new explorer window. Wenn der Benutzer auf die Schaltfläche „Neues Fenster“ klickt, starten wir eine Kopie des Fensters in einem separaten Thread.When the user clicks the "new window" button, we launch a copy of our window in a separate thread. Auf diese Weise sperren lange andauernde oder blockierende Vorgänge in einem der Fenster nicht alle anderen Fenster.This way, long-running or blocking operations in one of the windows won’t lock all the other windows.

In Wirklichkeit verfügt das Webbrowser-Modell über ein eigenes kompliziertes Threadingmodell.In reality, the Web browser model has its own complicated threading model. Wir haben uns dafür entschieden, da die meisten Leser damit vertraut sein sollten.We’ve chosen it because it should be familiar to most readers.

Im folgenden Beispiel wird der Code angezeigt.The following example shows the code.

<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:The following threading segments of this code are the most interesting to us in this context:

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.This method is called when the "new window" button is clicked. Sie erstellt einen neuen Thread und startet diesen asynchron.It creates a new thread and starts it asynchronously.

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.This method is the starting point for the new thread. Wir erstellen ein neues Fenster unter der Kontrolle dieses Threads.We create a new window under the control of this thread. WPFWPF erstellt automatisch eine neue Dispatcher, um den neuen Thread zu verwalten.automatically creates a new Dispatcher to manage the new thread. Wir müssen lediglich den Dispatcherstarten, um das Fenster funktionsfähig zu machen.All we have to do to make the window functional is to start the Dispatcher.

Technische Details und StolpersteineTechnical Details and Stumbling Points

Schreiben von Komponenten mithilfe von ThreadingWriting Components Using 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).The Microsoft .NET Framework Developer's Guide describes a pattern for how a component can expose asynchronous behavior to its clients (see Event-based Asynchronous Pattern Overview). Nehmen wir beispielsweise an, dass die FetchWeatherFromServer-Methode in eine wiederverwendbare, nicht grafische Komponente integriert werden soll.For instance, suppose we wanted to package the FetchWeatherFromServer method into a reusable, nongraphical component. Nach dem Standard-Microsoft .NET Framework-Muster würde dies in etwa wie folgt aussehen.Following the standard Microsoft .NET Framework pattern, this would look something like the following.

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.GetWeatherAsync would use one of the techniques described earlier, such as creating a background thread, to do the work asynchronously, not blocking the calling thread.

Einer der wichtigsten Teile dieses Musters ist das Aufrufen der MethodName -Completed-Methode für denselben Thread, der die MethodName -Async Methode aufgerufen hat, mit der begonnen werden soll.One of the most important parts of this pattern is calling the MethodNameCompleted method on the same thread that called the MethodNameAsync method to begin with. Dies könnten Sie mithilfe WPFWPF recht einfach tun, indem Sie CurrentDispatcherspeichern – aber die nicht grafische Komponente kann nur in WPFWPF Anwendungen verwendet werden, nicht in Windows FormsWindows Forms-oder ASP.NET-Programmen.You could do this using WPFWPF fairly easily, by storing CurrentDispatcher—but then the nongraphical component could only be used in WPFWPF applications, not in Windows FormsWindows Forms or ASP.NET programs.

Die DispatcherSynchronizationContext-Klasse erfüllt diese Anforderung – stellen Sie sich dies als vereinfachte Version von Dispatcher vor, die auch mit anderen UIUI Frameworks funktioniert.The DispatcherSynchronizationContext class addresses this need—think of it as a simplified version of Dispatcher that works with other UIUI frameworks as well.

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 VerteilungNested Pumping

Manchmal ist es nicht möglich, den UIUI Thread vollständig zu sperren.Sometimes it is not feasible to completely lock up the UIUI thread. Betrachten wir die Show-Methode der MessageBox-Klasse.Let’s consider the Show method of the MessageBox class. Show wird nicht zurückgegeben, bis der Benutzer auf die Schaltfläche OK klickt.Show doesn’t return until the user clicks the OK button. Es wird jedoch ein Fenster erstellt, das über eine Meldungsschleife verfügen muss, um interaktiv zu sein.It does, however, create a window that must have a message loop in order to be interactive. Während wir warten, bis der Benutzer auf „OK“ klickt, reagiert das ursprüngliche Anwendungsfenster nicht auf Benutzereingaben.While we are waiting for the user to click OK, the original application window does not respond to user input. Es verarbeitet jedoch weiterhin Paint-Meldungen.It does, however, continue to process paint messages. Das ursprüngliche Fenster zeichnet sich selbst neu, wenn es verdeckt war und dann wieder angezeigt wird.The original window redraws itself when covered and revealed.

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

Ein Thread muss für das Meldungsfenster zuständig sein.Some thread must be in charge of the message box window. WPFWPF 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).could create a new thread just for the message box window, but this thread would be unable to paint the disabled elements in the original window (remember the earlier discussion of mutual exclusion). Stattdessen verwendet WPFWPF ein System eigenes Nachrichten Verarbeitungssystem.Instead, WPFWPF uses a nested message processing system. 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.The Dispatcher class includes a special method called PushFrame, which stores an application’s current execution point then begins a new message loop. Wenn die schsted Message-Schleife abgeschlossen ist, wird die Ausführung nach dem ursprünglichen PushFrame aufgerufen.When the nested message loop finishes, execution resumes after the original PushFrame call.

In diesem Fall wird PushFrame den Programmkontext beim aufzurufen von MessageBox.Showverwaltet, und es wird eine neue Nachrichten Schleife gestartet, um das Hintergrund Fenster neu zu zeichnen und Eingaben in das Fenster "Meldungs Fenster" zu behandeln.In this case, PushFrame maintains the program context at the call to MessageBox.Show, and it starts a new message loop to repaint the background window and handle input to the message box window. 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 Showfortgesetzt.When the user clicks OK and clears the pop-up window, the nested loop exits and control resumes after the call to Show.

Veraltete RoutingereignisseStale Routed Events

Das Routing Ereignis System in WPFWPF benachrichtigt gesamte Strukturen, wenn Ereignisse ausgelöst werden.The routed event system in WPFWPF notifies entire trees when events are raised.

<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, wird handler2 ausgeführt.When the left mouse button is pressed over the ellipse, handler2 is executed. Nachdem handler2 abgeschlossen ist, wird das-Ereignis an das Canvas-Objekt weitergegeben, das handler1 verwendet, um es zu verarbeiten.After handler2 finishes, the event is passed along to the Canvas object, which uses handler1 to process it. Dies geschieht nur, wenn handler2 das Ereignis Objekt nicht explizit als behandelt kennzeichnet.This happens only if handler2 does not explicitly mark the event object as handled.

Es ist möglich, dass handler2 viel Zeit in Anspruch nimmt, dieses Ereignis zu verarbeiten.It’s possible that handler2 will take a great deal of time processing this event. handler2 können PushFrame verwenden, um eine schinstanzschleife zu starten, die für Stunden nicht zurückgibt.handler2 might use PushFrame to begin a nested message loop that doesn’t return for hours. 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.If handler2 does not mark the event as handled when this message loop is complete, the event is passed up the tree even though it is very old.

Wiedereintritt und SperrungReentrancy and Locking

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.The locking mechanism of the common language runtime (CLR) doesn’t behave exactly as one might imagine; one might expect a thread to cease operation completely when requesting a lock. In Wirklichkeit empfängt und verarbeitet der Thread weiterhin Meldungen mit hoher Priorität.In actuality, the thread continues to receive and process high-priority messages. Dadurch können Deadlocks vermieden und Schnittstellen minimal reaktionsfähig gemacht werden, aber dadurch können auch schwer erkennbare Fehler entstehen.This helps prevent deadlocks and make interfaces minimally responsive, but it introduces the possibility for subtle bugs. Der Großteil der Fälle, in denen Sie nichts wissen müssen, aber in seltenen Fällen (in der Regel Win32Win32 Fenster Nachrichten oder COM-STA-Komponenten) ist dies zu wissen.The vast majority of the time you don’t need to know anything about this, but under rare circumstances (usually involving Win32Win32 window messages or COM STA components) this can be worth knowing.

Die meisten Schnittstellen werden nicht mit Thread Sicherheit erstellt, da Entwickler daran arbeiten, dass ein UIUI nie von mehr als einem Thread aufgerufen wird.Most interfaces are not built with thread safety in mind because developers work under the assumption that a UIUI is never accessed by more than one thread. In diesem Fall kann es vorkommen, dass ein einzelner Thread Änderungen an der Umgebung zu unerwarteten Zeiten vornimmt, wodurch sich die negativen Auswirkungen der DispatcherObject gegenseitigen Ausschlussmechanismus lösen lassen.In this case, that single thread may make environmental changes at unexpected times, causing those ill effects that the DispatcherObject mutual exclusion mechanism is supposed to solve. Betrachten Sie den folgenden Pseudocode:Consider the following pseudocode:

Das Diagramm, das das wieder eintreten des Threading anzeigt.Diagram that shows threading reentrancy.

In den meisten Fällen ist dies die richtige Sache, aber es gibt Zeiten in WPFWPF, in denen eine solche unerwartete Wiederaufnahme wirklich Probleme verursachen kann.Most of the time that’s the right thing, but there are times in WPFWPF where such unexpected reentrancy can really cause problems. In bestimmten Schlüssel Zeiten ruft WPFWPF DisableProcessingauf, wodurch die Lock-Anweisung für diesen Thread so geändert wird, dass die WPFWPF Reentrancy-Sperre anstelle der üblichen CLR-Sperre verwendet wird.So, at certain key times, WPFWPF calls DisableProcessing, which changes the lock instruction for that thread to use the WPFWPF reentrancy-free lock, instead of the usual CLR lock.

Warum hat das CLR-Team dieses Verhalten gewählt?So why did the CLR team choose this behavior? Dies hatte mit COM-STA-Objekten und dem Finalizerthread zu tun.It had to do with COM STA objects and the finalization thread. Wenn für ein Objekt eine Garbage Collection durchgeführt wird, wird die Finalize-Methode im dedizierten Finalizer-Thread ausgeführt, nicht im UIUI Thread.When an object is garbage collected, its Finalize method is run on the dedicated finalizer thread, not the UIUI thread. Das Problem liegt darin, dass ein COM-STA-Objekt, das auf dem UIUI Thread erstellt wurde, nur im UIUI Thread verworfen werden kann.And therein lies the problem, because a COM STA object that was created on the UIUI thread can only be disposed on the UIUI thread. Die CLR übernimmt das Äquivalent eines BeginInvoke (in diesem Fall mit Win32's SendMessage).The CLR does the equivalent of a BeginInvoke (in this case using Win32’s SendMessage). Wenn der UIUI Thread jedoch ausgelastet ist, wird der Finalizerthread angehalten, und das COM-STA-Objekt kann nicht verworfen werden. dadurch entsteht ein schwerwiegender Speicherplatz.But if the UIUI thread is busy, the finalizer thread is stalled and the COM STA object can’t be disposed, which creates a serious memory leak. Das CLR-Team hat also den harten Aufruf getroffen, damit Sperren so funktionieren, wie Sie funktionieren.So the CLR team made the tough call to make locks work the way they do.

Der Task für WPFWPF besteht darin, unerwartete Eintritts Übereinstimmung zu vermeiden, ohne dass der Arbeits Speicherplatz neu eingeführt wird. aus diesem Grund blockieren wir den erneuten eintreten nicht überall.The task for WPFWPF is to avoid unexpected reentrancy without reintroducing the memory leak, which is why we don’t block reentrancy everywhere.

Siehe auchSee also