Ablaufinvarianz in asynchronen Anwendungen (C#)Handling Reentrancy in Async Apps (C#)

Wenn Sie asynchronen Code in der App einschließen, sollten Sie erneutes Eintreten, also den erneuten Beginn eines asynchronen Vorgangs vor seinem Abschließen, berücksichtigen und möglicherweise verhindern.When you include asynchronous code in your app, you should consider and possibly prevent reentrancy, which refers to reentering an asynchronous operation before it has completed. Wenn Sie Möglichkeiten für erneutes Eintreten nicht identifizieren und behandeln, kann dies zu unerwarteten Ergebnissen führen.If you don't identify and handle possibilities for reentrancy, it can cause unexpected results.

InhaltIn this topic

Hinweis

Um das Beispiel ausführen zu können, muss Visual Studio 2012 oder höher sowie .NET Framework 4.5 oder höher auf Ihrem Computer installiert sein.To run the example, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

Erkennen von AblaufinvarianzRecognizing Reentrancy

Im Beispiel in diesem Thema entscheiden sich Benutzer für eine Schaltfläche Start, um eine asynchrone App zu initiieren, mit der eine Reihe von Websites heruntergeladen und die Gesamtanzahl der heruntergeladenen Bytes berechnet werden.In the example in this topic, users choose a Start button to initiate an asynchronous app that downloads a series of websites and calculates the total number of bytes that are downloaded. Eine synchrone Version des Beispiels würde auf die gleiche Weise reagieren, unabhängig davon, wie oft ein Benutzer die Schaltfläche auswählt, da diese Ereignisse nach dem ersten Mal vom UI-Thread ignoriert werden, bis die Anwendung ausgeführt wurde.A synchronous version of the example would respond the same way regardless of how many times a user chooses the button because, after the first time, the UI thread ignores those events until the app finishes running. In einer asynchronen App reagiert der UI-Thread weiterhin, und Sie können möglicherweise erneut in den asynchronen Vorgang eintreten, bevor er abgeschlossen ist.In an asynchronous app, however, the UI thread continues to respond, and you might reenter the asynchronous operation before it has completed.

Im folgenden Beispiel wird die erwartete Ausgabe bei einmaliger Betätigung der Schaltfläche Start dargestellt.The following example shows the expected output if the user chooses the Start button only once. Eine Liste der heruntergeladenen Websites wird mit der Größe, in Bytes für jede Site, angezeigt.A list of the downloaded websites appears with the size, in bytes, of each site. Die Gesamtanzahl von Bytes wird am Ende angezeigt.The total number of bytes appears at the end.

1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
7. msdn.microsoft.com                                            42972  
8. msdn.microsoft.com/library/ff730837.aspx               146159  

TOTAL bytes returned:  890591  

Wenn der Benutzer die Schaltfläche allerdings mehrmals auswählt, wird der Ereignishandler wiederholt aufgerufen, und der Downloadvorgang beginnt jedes Mal erneut.However, if the user chooses the button more than once, the event handler is invoked repeatedly, and the download process is reentered each time. Daher werden mehrere asynchrone Vorgänge gleichzeitig ausgeführt, die Ausgabe überlappt mit den Ergebnissen, und die Gesamtzahl der Bytes ist verwirrend.As a result, several asynchronous operations are running at the same time, the output interleaves the results, and the total number of bytes is confusing.

1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
3. msdn.microsoft.com/en-us/library/jj155761.aspx                29019  
7. msdn.microsoft.com                                            42972  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
8. msdn.microsoft.com/library/ff730837.aspx               146159  

TOTAL bytes returned:  890591  

5. msdn.microsoft.com/library/hh524395.aspx                68959  
1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
7. msdn.microsoft.com                                            42972  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
8. msdn.microsoft.com/library/ff730837.aspx               146159  

TOTAL bytes returned:  890591  

6. msdn.microsoft.com/library/ms404677.aspx               197325  
7. msdn.microsoft.com                                            42972  
8. msdn.microsoft.com/library/ff730837.aspx               146159  

TOTAL bytes returned:  890591  

Sie können den Code, der diese Ausgabe erzeugt, überprüfen, indem Sie einen Bildlauf zum Ende dieses Themas durchführen.You can review the code that produces this output by scrolling to the end of this topic. Sie können mit dem Code experimentieren, indem Sie die Projektmappe auf den lokalen Computer herunterladen und das WebsiteDownload-Projekt ausführen, oder, indem Sie den Code am Ende dieses Themas zum Erstellen Ihres eigenen Projekts verwenden. Weitere Informationen und Anweisungen finden Sie unter Überprüfen und Ausführen der Beispiel-App.You can experiment with the code by downloading the solution to your local computer and then running the WebsiteDownload project or by using the code at the end of this topic to create your own project For more information and instructions, see Reviewing and Running the Example App.

Umgang mit AblaufinvarianzHandling Reentrancy

Sie können das erneute Eintreten auf verschiedene Weise behandeln, je nachdem, was von der App ausgeführt werden soll.You can handle reentrancy in a variety of ways, depending on what you want your app to do. In diesem Thema werden die folgenden Beispiele zur Veranschaulichung verwendet:This topic presents the following examples:

Die Schaltfläche „Start“ deaktivierenDisable the Start Button

Sie können die Schaltfläche Start während eines ausführenden Vorgangs blockieren, indem Sie die Schaltfläche oben im StartButton_Click-Ereignishandler deaktivieren.You can block the Start button while an operation is running by disabling the button at the top of the StartButton_Click event handler. Sie können die Schaltfläche aus einem finally-Block erneut aktivieren, sobald der Vorgang beendet ist, damit Benutzer die App erneut ausführen können.You can then reenable the button from within a finally block when the operation finishes so that users can run the app again.

Im folgenden Code werden diese Änderungen mit Sternchen gekennzeichnet dargestellt.The following code shows these changes, which are marked with asterisks. Sie können Änderungen am Code am Ende dieses Themas hinzufügen, oder Sie können die fertige App unter Async Samples: Reentrancy in .NET Desktop Apps (Asynchrone Beispiele: Ablaufinvarianz in .NET-Desktop-Apps) herunterladen.You can add the changes to the code at the end of this topic, or you can download the finished app from Async Samples: Reentrancy in .NET Desktop Apps. Der Projektname ist "DisableStartButton".The project name is DisableStartButton.

private async void StartButton_Click(object sender, RoutedEventArgs e)  
{  
    // This line is commented out to make the results clearer in the output.  
    //ResultsTextBox.Text = "";  

    // ***Disable the Start button until the downloads are complete.   
    StartButton.IsEnabled = false;   

    try  
    {  
        await AccessTheWebAsync();  
    }  
    catch (Exception)  
    {  
        ResultsTextBox.Text += "\r\nDownloads failed.";  
    }  
    // ***Enable the Start button in case you want to run the program again.   
    finally  
    {  
        StartButton.IsEnabled = true;  
    }  
}  

Aufgrund der Änderungen reagiert die Schaltfläche nicht, während die Websites von AccessTheWebAsync heruntergeladen werden, sodass ein erneutes Eintreten in den Prozess nicht möglich ist.As a result of the changes, the button doesn't respond while AccessTheWebAsync is downloading the websites, so the process can’t be reentered.

Den Vorgang abbrechen und neu startenCancel and Restart the Operation

Anstatt die Schaltfläche Start zu deaktivieren, kann die Schaltfläche aktiv bleiben. Wenn der Benutzer die Schaltfläche dann erneut anklickt, brechen Sie den bereits ausgeführten Vorgang ab und lassen den zuletzt begonnenen Vorgang fortsetzen.Instead of disabling the Start button, you can keep the button active but, if the user chooses that button again, cancel the operation that's already running and let the most recently started operation continue.

Weitere Informationen zum Abbrechen finden Sie unter Fine-Tuning Your Async Application (C#) (Abstimmen der asynchronen Anwendung (C#)).For more information about cancellation, see Fine-Tuning Your Async Application (C#).

Um dieses Szenario festzulegen, nehmen Sie am grundlegenden Code aus Überprüfen und Ausführen der Beispiel-App folgende Änderungen vor.To set up this scenario, make the following changes to the basic code that is provided in Reviewing and Running the Example App. Sie können die fertige App auch unter Async Samples: Reentrancy in .NET Desktop Apps (Asynchrone Beispiele: Ablaufinvarianz in .NET-Desktop-Apps) herunterladen.You also can download the finished app from Async Samples: Reentrancy in .NET Desktop Apps. Der Name dieses Projekts lautet "CancelAndRestart".The name of this project is CancelAndRestart.

  1. Deklarieren Sie eine CancellationTokenSource-Variable, cts, die im Bereich für alle Methoden liegt.Declare a CancellationTokenSource variable, cts, that’s in scope for all methods.

    public partial class MainWindow : Window   // Or class MainPage  
    {  
        // *** Declare a System.Threading.CancellationTokenSource.  
        CancellationTokenSource cts;  
    
  2. Bestimmen Sie im Element StartButton_Click, ob ein Vorgang bereits ausgeführt wird.In StartButton_Click, determine whether an operation is already underway. Wenn der Wert von cts 0 (null) lautet, ist noch kein Vorgang aktiv.If the value of cts is null, no operation is already active. Wenn der Wert nicht NULL ist, wird der Vorgang, der bereits ausgeführt wird, abgebrochen.If the value isn't null, the operation that is already running is canceled.

    // *** If a download process is already underway, cancel it.  
    if (cts != null)  
    {  
        cts.Cancel();  
    }  
    
  3. Legen Sie cts auf einen anderen Wert fest, der den aktuellen Prozess darstellt.Set cts to a different value that represents the current process.

    // *** Now set cts to a new value that you can use to cancel the current process  
    // if the button is chosen again.  
    CancellationTokenSource newCTS = new CancellationTokenSource();  
    cts = newCTS;  
    
  4. Am Ende von StartButton_Click ist der aktuelle Prozess abgeschlossen. Setzen Sie den Wert für cts also zurück auf NULL.At the end of StartButton_Click, the current process is complete, so set the value of cts back to null.

    // *** When the process is complete, signal that another process can begin.  
    if (cts == newCTS)  
        cts = null;  
    

Im folgende Code werden alle Änderungen in StartButton_Click dargestellt.The following code shows all the changes in StartButton_Click. Die Ergänzungen werden mit Sternchen gekennzeichnet.The additions are marked with asterisks.

private async void StartButton_Click(object sender, RoutedEventArgs e)  
{  
    // This line is commented out to make the results clearer in the output.  
    //ResultsTextBox.Clear();  

    // *** If a download process is already underway, cancel it.  
    if (cts != null)  
    {  
        cts.Cancel();  
    }  

    // *** Now set cts to cancel the current process if the button is chosen again.  
    CancellationTokenSource newCTS = new CancellationTokenSource();  
    cts = newCTS;  

    try  
    {  
        // ***Send cts.Token to carry the message if there is a cancellation request.  
        await AccessTheWebAsync(cts.Token);  

    }  
    // *** Catch cancellations separately.  
    catch (OperationCanceledException)  
    {  
        ResultsTextBox.Text += "\r\nDownloads canceled.\r\n";  
    }  
    catch (Exception)  
    {  
        ResultsTextBox.Text += "\r\nDownloads failed.\r\n";  
    }  
    // *** When the process is complete, signal that another process can proceed.  
    if (cts == newCTS)  
        cts = null;  
}  

Nehmen Sie dazu in AccessTheWebAsyncdie folgenden Änderungen vor.In AccessTheWebAsync, make the following changes.

  • Fügen Sie einen Parameter hinzu, um das Abbruchtoken von StartButton_Click zu akzeptieren.Add a parameter to accept the cancellation token from StartButton_Click.

  • Verwenden Sie die GetAsync-Methode zum Herunterladen der Websites, da GetAsync ein CancellationToken Argument akzeptiert.Use the GetAsync method to download the websites because GetAsync accepts a CancellationToken argument.

  • Bevor Sie DisplayResults aufrufen, um die Ergebnisse für jede heruntergeladene Website anzuzeigen, aktivieren Sie ct, um sicherzustellen, dass der aktuelle Vorgang nicht abgebrochen wurde.Before calling DisplayResults to display the results for each downloaded website, check ct to verify that the current operation hasn’t been canceled.

Im folgenden Code werden diese Änderungen mit Sternchen gekennzeichnet dargestellt.The following code shows these changes, which are marked with asterisks.

// *** Provide a parameter for the CancellationToken from StartButton_Click.  
async Task AccessTheWebAsync(CancellationToken ct)  
{  
    // Declare an HttpClient object.  
    HttpClient client = new HttpClient();  

    // Make a list of web addresses.  
    List<string> urlList = SetUpURLList();  

    var total = 0;  
    var position = 0;  

    foreach (var url in urlList)  
    {  
        // *** Use the HttpClient.GetAsync method because it accepts a   
        // cancellation token.  
        HttpResponseMessage response = await client.GetAsync(url, ct);  

        // *** Retrieve the website contents from the HttpResponseMessage.  
        byte[] urlContents = await response.Content.ReadAsByteArrayAsync();  

        // *** Check for cancellations before displaying information about the   
        // latest site.   
        ct.ThrowIfCancellationRequested();  

        DisplayResults(url, urlContents, ++position);  

        // Update the total.  
        total += urlContents.Length;  
    }  

    // Display the total count for all of the websites.  
    ResultsTextBox.Text +=  
        string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);  
}     

Wenn Sie die Schaltfläche Start mehrmals anklicken, während diese App ausgeführt wird, sollten Ergebnisse erzeugt werden, die der folgenden Ausgabe ähneln.If you choose the Start button several times while this app is running, it should produce results that resemble the following output.

1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               122505  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
Download canceled.  

1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
Download canceled.  

1. msdn.microsoft.com/library/hh191443.aspx                83732  
2. msdn.microsoft.com/library/aa578028.aspx               205273  
3. msdn.microsoft.com/library/jj155761.aspx                29019  
4. msdn.microsoft.com/library/hh290140.aspx               117152  
5. msdn.microsoft.com/library/hh524395.aspx                68959  
6. msdn.microsoft.com/library/ms404677.aspx               197325  
7. msdn.microsoft.com                                            42972  
8. msdn.microsoft.com/library/ff730837.aspx               146159  

TOTAL bytes returned:  890591  

Zum Ausschließen der Teillisten entfernen Sie die Kommentarmarkierungen der ersten Codezeile in StartButton_Click, um das Textfeld bei jedem erneuten Start des Vorgangs zu löschen.To eliminate the partial lists, uncomment the first line of code in StartButton_Click to clear the text box each time the user restarts the operation.

Mehrere Vorgänge ausführen und die Ausgabe in eine Warteschlange stellenRun Multiple Operations and Queue the Output

Das dritte Beispiel ist das schwierigste, da von der App jedes Mal, wenn der Benutzer die Schaltfläche Start anklickt, ein anderer asynchroner Vorgang gestartet wird und alle Vorgänge vollständig ausgeführt werden.This third example is the most complicated in that the app starts another asynchronous operation each time that the user chooses the Start button, and all the operations run to completion. Alle angeforderten Vorgänge laden Websites asynchron aus der Liste herunter, doch die Ausgabe der Vorgänge wird sequenziell dargestellt.All the requested operations download websites from the list asynchronously, but the output from the operations is presented sequentially. Das bedeutet, die tatsächliche Downloadaktivität überlappt, wie es die Ausgabe in Erkennen von Ablaufinvarianz zeigt, die Ergebnislisten für jede Gruppe aber getrennt angezeigt werden.That is, the actual downloading activity is interleaved, as the output in Recognizing Reentrancy shows, but the list of results for each group is presented separately.

Die Vorgänge geben global Task, pendingWork frei, der als Gatekeeper für den Anzeigenprozess dient.The operations share a global Task, pendingWork, which serves as a gatekeeper for the display process.

Sie können dieses Beispiel ausführen, indem Sie die Änderungen in den Code in Erstellen der App einfügen, oder Sie können den Anweisungen in Herunterladen der App folgen, um das Beispiel herunterzuladen und dann das QueueResults-Projekt auszuführen.You can run this example by pasting the changes into the code in Building the App, or you can follow the instructions in Downloading the App to download the sample and then run the QueueResults project.

Die folgende Ausgabe zeigt das Ergebnis bei einmaliger Betätigung der Schaltfläche Start.The following output shows the result if the user chooses the Start button only once. Die Buchstabenbezeichnung „A“ gibt an, dass das Ergebnis vom ersten Klick auf die Schaltfläche Start stammt.The letter label, A, indicates that the result is from the first time the Start button is chosen. Die Zahlen geben die Reihenfolge der URL in der Liste der Downloadziele wieder.The numbers show the order of the URLs in the list of download targets.

#Starting group A.  
#Task assigned for group A.  

A-1. msdn.microsoft.com/library/hh191443.aspx                87389  
A-2. msdn.microsoft.com/library/aa578028.aspx               209858  
A-3. msdn.microsoft.com/library/jj155761.aspx                30870  
A-4. msdn.microsoft.com/library/hh290140.aspx               119027  
A-5. msdn.microsoft.com/library/hh524395.aspx                71260  
A-6. msdn.microsoft.com/library/ms404677.aspx               199186  
A-7. msdn.microsoft.com                                            53266  
A-8. msdn.microsoft.com/library/ff730837.aspx               148020  

TOTAL bytes returned:  918876  

#Group A is complete.  

Wenn der Benutzer die Schaltfläche Start dreimal anklickt, erzeugt die App eine Ausgabe, die den folgenden Zeilen ähnelt.If the user chooses the Start button three times, the app produces output that resembles the following lines. Die Informationszeilen, die mit einem Nummernzeichen (#) beginnen, verfolgen den Status der Anwendung.The information lines that start with a pound sign (#) trace the progress of the application.

#Starting group A.  
#Task assigned for group A.  

A-1. msdn.microsoft.com/library/hh191443.aspx                87389  
A-2. msdn.microsoft.com/library/aa578028.aspx               207089  
A-3. msdn.microsoft.com/library/jj155761.aspx                30870  
A-4. msdn.microsoft.com/library/hh290140.aspx               119027  
A-5. msdn.microsoft.com/library/hh524395.aspx                71259  
A-6. msdn.microsoft.com/library/ms404677.aspx               199185  

#Starting group B.  
#Task assigned for group B.  

A-7. msdn.microsoft.com                                            53266  

#Starting group C.  
#Task assigned for group C.  

A-8. msdn.microsoft.com/library/ff730837.aspx               148010  

TOTAL bytes returned:  916095  

B-1. msdn.microsoft.com/library/hh191443.aspx                87389  
B-2. msdn.microsoft.com/library/aa578028.aspx               207089  
B-3. msdn.microsoft.com/library/jj155761.aspx                30870  
B-4. msdn.microsoft.com/library/hh290140.aspx               119027  
B-5. msdn.microsoft.com/library/hh524395.aspx                71260  
B-6. msdn.microsoft.com/library/ms404677.aspx               199186  

#Group A is complete.  

B-7. msdn.microsoft.com                                            53266  
B-8. msdn.microsoft.com/library/ff730837.aspx               148010  

TOTAL bytes returned:  916097  

C-1. msdn.microsoft.com/library/hh191443.aspx                87389  
C-2. msdn.microsoft.com/library/aa578028.aspx               207089  

#Group B is complete.  

C-3. msdn.microsoft.com/library/jj155761.aspx                30870  
C-4. msdn.microsoft.com/library/hh290140.aspx               119027  
C-5. msdn.microsoft.com/library/hh524395.aspx                72765  
C-6. msdn.microsoft.com/library/ms404677.aspx               199186  
C-7. msdn.microsoft.com                                            56190  
C-8. msdn.microsoft.com/library/ff730837.aspx               148010  

TOTAL bytes returned:  920526  

#Group C is complete.  

Die Gruppen B und C starten, bevor Gruppe A beendet ist, doch die Ausgabe für jede Gruppe wird getrennt angezeigt.Groups B and C start before group A has finished, but the output for the each group appears separately. Die gesamte Ausgabe für Gruppe A wird zuerst angezeigt, gefolgt von der gesamten Ausgabe für Gruppe B und dann die gesamte Ausgabe für Gruppe C. Die App zeigt die Gruppen immer in Reihenfolge an, und die Informationen zu den einzelnen Websites werden immer in der Reihenfolge angezeigt, in der die URLs in der URL-Liste aufgeführt werden.All the output for group A appears first, followed by all the output for group B, and then all the output for group C. The app always displays the groups in order and, for each group, always displays the information about the individual websites in the order that the URLs appear in the list of URLs.

Sie können die Reihenfolge, in der die Downloads tatsächlich vorkommen, allerdings nicht vorhersagen.However, you can't predict the order in which the downloads actually happen. Nachdem mehrere Gruppen gestartet wurden, sind alle von ihnen generierten Downloadtasks aktiv.After multiple groups have been started, the download tasks that they generate are all active. Sie können nicht davon ausgehen, dass A-1 vor B-1 heruntergeladen wird, und Sie können auch nicht davon ausgehen, dass A-1 vor A-2 heruntergeladen wird.You can't assume that A-1 will be downloaded before B-1, and you can't assume that A-1 will be downloaded before A-2.

Globale DefinitionenGlobal Definitions

Im Beispielcode sind die folgenden zwei globalen Deklarationen enthalten, die von allen Methoden sichtbar sind.The sample code contains the following two global declarations that are visible from all methods.

public partial class MainWindow : Window  // Class MainPage in Windows Store app.  
{  
    // ***Declare the following variables where all methods can access them.   
    private Task pendingWork = null;     
    private char group = (char)('A' - 1);  

Mit der Task-Variable pendingWork wird der Anzeigenprozess beaufsichtigt, und es wird verhindert, dass der Anzeigevorgang einer Gruppe zur Unterbrechung des Anzeigevorgangs einer anderen Gruppe führt.The Task variable, pendingWork, oversees the display process and prevents any group from interrupting another group's display operation. Die Zeichenvariable group bezeichnet die Ausgabe von unterschiedlichen Gruppen, um sicherzustellen, dass Ergebnisse in der erwarteten Reihenfolge angezeigt werden.The character variable, group, labels the output from different groups to verify that results appear in the expected order.

Der Click-EreignishandlerThe Click Event Handler

Mit dem Ereignishandler StartButton_Click wird der Gruppenbuchstabe bei jedem Auswählen der Schaltfläche Start erhöht.The event handler, StartButton_Click, increments the group letter each time the user chooses the Start button. Anschließend ruft der Handler zum Ausführen des Downloadingvorgangs das Element AccessTheWebAsync auf.Then the handler calls AccessTheWebAsync to run the downloading operation.

private async void StartButton_Click(object sender, RoutedEventArgs e)  
{  
    // ***Verify that each group's results are displayed together, and that  
    // the groups display in order, by marking each group with a letter.  
    group = (char)(group + 1);  
    ResultsTextBox.Text += string.Format("\r\n\r\n#Starting group {0}.", group);  

    try  
    {  
        // *** Pass the group value to AccessTheWebAsync.  
        char finishedGroup = await AccessTheWebAsync(group);  

        // The following line verifies a successful return from the download and  
        // display procedures.   
        ResultsTextBox.Text += string.Format("\r\n\r\n#Group {0} is complete.\r\n", finishedGroup);  
    }  
    catch (Exception)  
    {  
        ResultsTextBox.Text += "\r\nDownloads failed.";  
    }  
}  

Die AccessTheWebAsync-MethodeThe AccessTheWebAsync Method

In diesem Beispiel wird AccessTheWebAsync in zwei Methoden aufgeteilt.This example splits AccessTheWebAsync into two methods. Mit der erste Methode, AccessTheWebAsync, werden alle Downloadtasks für eine Gruppe gestartet und pendingWork wird zum Steuern des Anzeigenprozesses festgelegt.The first method, AccessTheWebAsync, starts all the download tasks for a group and sets up pendingWork to control the display process. Die Methode verwendet zum gleichzeitigen Starten aller Downloadtasks eine LINQ-Abfrage (Language-Integrated Query) sowie ToArray.The method uses a Language Integrated Query (LINQ query) and ToArray to start all the download tasks at the same time.

AccessTheWebAsync ruft dann FinishOneGroupAsync auf, um den Abschluss jedes einzelnen Downloads zu erwarten und die Länge anzuzeigen.AccessTheWebAsync then calls FinishOneGroupAsync to await the completion of each download and display its length.

FinishOneGroupAsync gibt einen Task zurück, der in pendingWork dem Element AccessTheWebAsync zugewiesen wird.FinishOneGroupAsync returns a task that's assigned to pendingWork in AccessTheWebAsync. Dieser Wert verhindert die Unterbrechung durch einen anderen Vorgang, bevor die Aufgabe abgeschlossen wurde.That value prevents interruption by another operation before the task is complete.

private async Task<char> AccessTheWebAsync(char grp)  
{  
    HttpClient client = new HttpClient();  

    // Make a list of the web addresses to download.  
    List<string> urlList = SetUpURLList();  

    // ***Kick off the downloads. The application of ToArray activates all the download tasks.  
    Task<byte[]>[] getContentTasks = urlList.Select(url => client.GetByteArrayAsync(url)).ToArray();  

    // ***Call the method that awaits the downloads and displays the results.  
    // Assign the Task that FinishOneGroupAsync returns to the gatekeeper task, pendingWork.  
    pendingWork = FinishOneGroupAsync(urlList, getContentTasks, grp);  

    ResultsTextBox.Text += string.Format("\r\n#Task assigned for group {0}. Download tasks are active.\r\n", grp);  

    // ***This task is complete when a group has finished downloading and displaying.  
    await pendingWork;  

    // You can do other work here or just return.  
    return grp;  
}  

Die FinishOneGroupAsync-MethodeThe FinishOneGroupAsync Method

Diese Methode durchläuft die Downloadaufgaben in einer Gruppe, erwartet dabei jeden einzelnen Task, zeigt die Länge der heruntergeladenen Website an und addiert diese Länge zur Summe.This method cycles through the download tasks in a group, awaiting each one, displaying the length of the downloaded website, and adding the length to the total.

Die erste Anweisung in FinishOneGroupAsync verwendet pendingWork, um sicherzustellen, dass die Eingabe der Methode keinen Vorgang behindert, der sich bereits im Anzeige- oder im Warteprozess befindet.The first statement in FinishOneGroupAsync uses pendingWork to make sure that entering the method doesn't interfere with an operation that is already in the display process or that's already waiting. Wenn ein solcher Vorgang ausgeführt wird, wird der eintretende Vorgang solange aufgeschoben, bis er an der Reihe ist.If such an operation is in progress, the entering operation must wait its turn.

private async Task FinishOneGroupAsync(List<string> urls, Task<byte[]>[] contentTasks, char grp)  
{  
    // ***Wait for the previous group to finish displaying results.  
    if (pendingWork != null) await pendingWork;  

    int total = 0;  

    // contentTasks is the array of Tasks that was created in AccessTheWebAsync.  
    for (int i = 0; i < contentTasks.Length; i++)  
    {  
        // Await the download of a particular URL, and then display the URL and  
        // its length.  
        byte[] content = await contentTasks[i];  
        DisplayResults(urls[i], content, i, grp);  
        total += content.Length;  
    }  

    // Display the total count for all of the websites.  
    ResultsTextBox.Text +=  
        string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);  
}  

Sie können dieses Beispiel ausführen, indem Sie die Änderungen in den Code in Erstellen der App einfügen, oder Sie können den Anweisungen in Herunterladen der App folgen, um das Beispiel herunterzuladen und dann das QueueResults-Projekt auszuführen.You can run this example by pasting the changes into the code in Building the App, or you can follow the instructions in Downloading the App to download the sample, and then run the QueueResults project.

Relevante PunktePoints of Interest

Die Informationszeilen, die mit einem Nummernzeichen (#) in der Ausgabe beginnen, erläutern die Funktionsweise dieses Beispiels.The information lines that start with a pound sign (#) in the output clarify how this example works.

Die Ausgabe zeigt die folgenden Muster an.The output shows the following patterns.

  • Eine Gruppe kann gestartet werden, während die Ausgabe einer vorherigen Gruppe angezeigt wird. Doch die Anzeige der Ausgabe der vorherigen Gruppe wird nicht unterbrochen.A group can be started while a previous group is displaying its output, but the display of the previous group's output isn't interrupted.

    #Starting group A.  
    #Task assigned for group A. Download tasks are active.  
    
    A-1. msdn.microsoft.com/library/hh191443.aspx                87389  
    A-2. msdn.microsoft.com/library/aa578028.aspx               207089  
    A-3. msdn.microsoft.com/library/jj155761.aspx                30870  
    A-4. msdn.microsoft.com/library/hh290140.aspx               119037  
    A-5. msdn.microsoft.com/library/hh524395.aspx                71260  
    
    #Starting group B.  
    #Task assigned for group B. Download tasks are active.  
    
    A-6. msdn.microsoft.com/library/ms404677.aspx               199186  
    A-7. msdn.microsoft.com                                            53078  
    A-8. msdn.microsoft.com/library/ff730837.aspx               148010  
    
    TOTAL bytes returned:  915919  
    
    B-1. msdn.microsoft.com/library/hh191443.aspx                87388  
    B-2. msdn.microsoft.com/library/aa578028.aspx               207089  
    B-3. msdn.microsoft.com/library/jj155761.aspx                30870  
    
    #Group A is complete.  
    
    B-4. msdn.microsoft.com/library/hh290140.aspx               119027  
    B-5. msdn.microsoft.com/library/hh524395.aspx                71260  
    B-6. msdn.microsoft.com/library/ms404677.aspx               199186  
    B-7. msdn.microsoft.com                                            53078  
    B-8. msdn.microsoft.com/library/ff730837.aspx               148010  
    
    TOTAL bytes returned:  915908  
    
  • Die pendingWork-Aufgabe ist zu Beginn von FinishOneGroupAsync nur für Gruppe A, die zuerst gestartet wurde, 0 (null).The pendingWork task is null at the start of FinishOneGroupAsync only for group A, which started first. Gruppe A hat bei Erreichen von FinishOneGroupAsync einen Erwartungsausdruck noch nicht abgeschlossen.Group A hasn’t yet completed an await expression when it reaches FinishOneGroupAsync. Daher wurde die Steuerung nicht an AccessTheWebAsync zurückgegeben, und die erste Zuweisung zu pendingWork ist nicht aufgetreten.Therefore, control hasn't returned to AccessTheWebAsync, and the first assignment to pendingWork hasn't occurred.

  • Die folgenden zwei Zeilen werden immer zusammen in der Ausgabe angezeigt.The following two lines always appear together in the output. Der Code wird zwischen dem Starten des Vorgangs einer Gruppe in StartButton_Click und dem Zuweisen eines Tasks für die Gruppe zu pendingWork nie unterbrochen.The code is never interrupted between starting a group's operation in StartButton_Click and assigning a task for the group to pendingWork.

    #Starting group B.  
    #Task assigned for group B. Download tasks are active.  
    

    Nachdem eine Gruppe in StartButton_Click eintritt, schließt der Vorgang einen Erwartungsausdruck erst ab, wenn der Vorgang in FinishOneGroupAsync eintritt.After a group enters StartButton_Click, the operation doesn't complete an await expression until the operation enters FinishOneGroupAsync. Daher kann kein weiterer Vorgang während dieses Codeabschnitts die Steuerung übernehmen.Therefore, no other operation can gain control during that segment of code.

Die Beispiel-App überprüfen und ausführenReviewing and Running the Example App

Zum besseren Verständnis der Beispiel-App können Sie sie herunterladen, sie selbst erstellen oder den Code am Ende dieses Themas überprüfen, ohne die App zu implementieren.To better understand the example app, you can download it, build it yourself, or review the code at the end of this topic without implementing the app.

Hinweis

Um die App des Beispiels als WPF-Desktop-App (Windows Presentation Foundation) auszuführen, muss Visual Studio 2012 oder höher oder .NET Framework 4.5 oder höher auf dem Computer installiert sein.To run the example as a Windows Presentation Foundation (WPF) desktop app, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

Herunterladen der AppDownloading the App

  1. Sie können die komprimierte Datei unter Async Samples: Reentrancy in .NET Desktop Apps (Asynchrone Beispiele: Ablaufinvarianz in .NET-Desktop-Apps) herunterladen.Download the compressed file from Async Samples: Reentrancy in .NET Desktop Apps.

  2. Dekomprimieren Sie die heruntergeladene Datei, und starten Sie dann Visual Studio.Decompress the file that you downloaded, and then start Visual Studio.

  3. Klicken Sie in der Menüleiste auf Datei, dann auf Öffnenund Projekt/Projektmappe.On the menu bar, choose File, Open, Project/Solution.

  4. Navigieren Sie zu dem Ordner mit dem dekomprimierten Beispielcode, und öffnen Sie die Projektmappendatei (SLN-Datei).Navigate to the folder that holds the decompressed sample code, and then open the solution (.sln) file.

  5. Öffnen Sie im Projektmappen-Explorer das Kontextmenü für das Projekt, das Sie ausführen möchten, und wählen Sie dann Set as StartUpProject (Als StartUpProject festlegen) aus.In Solution Explorer, open the shortcut menu for the project that you want to run, and then choose Set as StartUpProject.

  6. Drücken Sie zum Erstellen und Ausführen des Projekts die Tastenkombination "STRG+F5".Choose the CTRL+F5 keys to build and run the project.

Erstellen der AppBuilding the App

Der folgende Abschnitt enthält den Code, um das Beispiel als WPF-App zu erstellen.The following section provides the code to build the example as a WPF app.

So erstellen Sie eine WPF-AppTo build a WPF app
  1. Starten Sie Visual Studio.Start Visual Studio.

  2. Wählen Sie in der Menüleiste Datei, Neu, Projektaus.On the menu bar, choose File, New, Project.

    Das Dialogfeld Neues Projekt wird angezeigt.The New Project dialog box opens.

  3. Erweitern Sie im Bereich Installed Templates (Installierte Vorlagen) den Eintrag Visual C#, und erweitern Sie dann Windows.In the Installed Templates pane, expand Visual C#, and then expand Windows.

  4. Wählen Sie in der Liste der Projekttypen WPF-Anwendung aus.In the list of project types, choose WPF Application.

  5. Weisen Sie dem Projekt den Namen WebsiteDownloadWPF zu, und wählen Sie dann die Schaltfläche OK aus.Name the project WebsiteDownloadWPF, and then choose the OK button.

    Das neue Projekt wird im Projektmappen-Explorer angezeigt.The new project appears in Solution Explorer.

  6. Wählen Sie im Visual Studio Code Editor die Registerkarte MainWindow.xaml aus.In the Visual Studio Code Editor, choose the MainWindow.xaml tab.

    Wenn die Registerkarte nicht sichtbar ist, öffnen Sie das Kontextmenü für „MainWindow.xaml“ im Projektmappen-Explorer, und wählen Sie dann Code anzeigenaus.If the tab isn’t visible, open the shortcut menu for MainWindow.xaml in Solution Explorer, and then choose View Code.

  7. Ersetzen Sie den automatisch generierten Code in der XAML-Ansicht der Datei „MainWindow.xaml“ durch den folgenden Code.In the XAML view of MainWindow.xaml, replace the code with the following code.

    <Window x:Class="WebsiteDownloadWPF.MainWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:local="using:WebsiteDownloadWPF"  
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
        mc:Ignorable="d">  
    
        <Grid Width="517" Height="360">  
            <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="-1,0,0,0" VerticalAlignment="Top" Click="StartButton_Click" Height="53" Background="#FFA89B9B" FontSize="36" Width="518"  />  
            <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" Margin="-1,53,0,-36" TextWrapping="Wrap" VerticalAlignment="Top" Height="343" FontSize="10" ScrollViewer.VerticalScrollBarVisibility="Visible" Width="518" FontFamily="Lucida Console" />  
        </Grid>  
    </Window>  
    

    Ein einfaches Fenster, das ein Textfeld und eine Schaltfläche enthält, wird in der Entwurfsansicht der Datei „MainWindow.xaml“ angezeigt.A simple window that contains a text box and a button appears in the Design view of MainWindow.xaml.

  8. Fügen Sie einen Verweis für System.Net.Http hinzu.Add a reference for System.Net.Http.

  9. Öffnen Sie im Projektmappen-Explorer das Kontextmenü für „MainWindow.xaml.cs“, und wählen Sie dann Code anzeigen aus.In Solution Explorer, open the shortcut menu for MainWindow.xaml.cs, and then choose View Code.

  10. Ersetzen Sie den Code in „MainWindow.xaml.cs“ durch den folgenden Code.In MainWindow.xaml.cs, replace the code with the following code.

    using System;  
    using System.Collections.Generic;  
    using System.Linq;  
    using System.Text;  
    using System.Threading.Tasks;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
    using System.Windows.Documents;  
    using System.Windows.Input;  
    using System.Windows.Media;  
    using System.Windows.Media.Imaging;  
    using System.Windows.Navigation;  
    using System.Windows.Shapes;  
    
    // Add the following using directives, and add a reference for System.Net.Http.  
    using System.Net.Http;  
    using System.Threading;  
    
    namespace WebsiteDownloadWPF  
    {  
        public partial class MainWindow : Window  
        {  
            public MainWindow()  
            {  
                InitializeComponent();  
            }  
    
            private async void StartButton_Click(object sender, RoutedEventArgs e)  
            {  
                // This line is commented out to make the results clearer in the output.  
                //ResultsTextBox.Text = "";  
    
                try  
                {  
                    await AccessTheWebAsync();  
                }  
                catch (Exception)  
                {  
                    ResultsTextBox.Text += "\r\nDownloads failed.";  
                }  
            }  
    
            private async Task AccessTheWebAsync()  
            {  
                // Declare an HttpClient object.  
                HttpClient client = new HttpClient();  
    
                // Make a list of web addresses.  
                List<string> urlList = SetUpURLList();  
    
                var total = 0;  
                var position = 0;  
    
                foreach (var url in urlList)  
                {  
                    // GetByteArrayAsync returns a task. At completion, the task  
                    // produces a byte array.  
                    byte[] urlContents = await client.GetByteArrayAsync(url);  
    
                    DisplayResults(url, urlContents, ++position);  
    
                    // Update the total.  
                    total += urlContents.Length;  
                }  
    
                // Display the total count for all of the websites.  
                ResultsTextBox.Text +=  
                    string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);  
            }  
    
            private List<string> SetUpURLList()  
            {  
                List<string> urls = new List<string>   
                {   
                    "http://msdn.microsoft.com/library/hh191443.aspx",  
                    "http://msdn.microsoft.com/library/aa578028.aspx",  
                    "http://msdn.microsoft.com/library/jj155761.aspx",  
                    "http://msdn.microsoft.com/library/hh290140.aspx",  
                    "http://msdn.microsoft.com/library/hh524395.aspx",  
                    "http://msdn.microsoft.com/library/ms404677.aspx",  
                    "http://msdn.microsoft.com",  
                    "http://msdn.microsoft.com/library/ff730837.aspx"  
                };  
                return urls;  
            }  
    
            private void DisplayResults(string url, byte[] content, int pos)  
            {  
                // Display the length of each website. The string format is designed  
                // to be used with a monospaced font, such as Lucida Console or   
                // Global Monospace.  
    
                // Strip off the "http://".  
                var displayURL = url.Replace("http://", "");  
                // Display position in the URL list, the URL, and the number of bytes.  
                ResultsTextBox.Text += string.Format("\n{0}. {1,-58} {2,8}", pos, displayURL, content.Length);  
            }  
        }  
    }  
    
  11. Wählen Sie zum Ausführen des Programms die Tastenkombination „STRG+F5“ aus, und wählen Sie dann mehrmals die Schaltfläche Start aus.Choose the CTRL+F5 keys to run the program, and then choose the Start button several times.

  12. Nehmen Sie die Änderungen aus Die Schaltfläche „Start“ deaktivieren, Den Vorgang abbrechen und neu starten oder Mehrere Vorgänge ausführen und die Ausgabe in eine Warteschlange stellen vor, um mit Ablaufinvarianz umzugehen.Make the changes from Disable the Start Button, Cancel and Restart the Operation, or Run Multiple Operations and Queue the Output to handle the reentrancy.

Siehe auchSee Also

Exemplarische Vorgehensweise: Zugreifen auf das Web mit „async“ und „await“ (C#)Walkthrough: Accessing the Web by Using async and await (C#)
Asynchronous Programming with async and await (C#) (Asynchrone Programmierung mit Async und Await (C#))Asynchronous Programming with async and await (C#)