Ablaufsteuerung in asynchronen Programmen (C# und Visual Basic)
Sie können asynchrone Programme mithilfe der Schlüsselwörter Async und Await einfacher schreiben und verwalten. Möglicherweise könnten Sie jedoch die Ergebnisse überraschen, wenn Sie die Funktionsweise Ihres Programms nicht verstehen. In diesem Thema wird die Ablaufsteuerung durch ein einfaches asynchrones Programm nachvollzogen, um darzustellen, wann die Steuerung von einer Methode zu einer anderen springt und welche Informationen jedes Mal übertragen werden.
Hinweis
Die Schlüsselwörter Async und Await wurden in Visual Studio 2012 eingeführt.
Im Allgemeinen markieren Sie Methoden mit asynchronem Code mithilfe des Async (Visual Basic)- oder async (C#)-Modifizierers. In einer Methode, die mit einem asynchronen Modifizierer markiert ist, können Sie einen Await (Visual Basic)- oder await (C#)-Operator verwenden, um anzugeben, wo die Methode anhält, um darauf zu warten, dass ein aufgerufener asynchroner Prozess abgeschlossen wird. Weitere Informationen finden Sie unter Asynchrone Programmierung mit Async und Await (C# und Visual Basic).
Im folgenden Beispiel werden asynchrone Methoden verwendet, um den Inhalt einer angegebenen Website als Zeichenfolge herunterzuladen und um die Länge der Zeichenfolge anzuzeigen. Das Beispiel enthält die folgenden beiden Methoden.
startButton_Click, die AccessTheWebAsync aufruft und das Ergebnis angezeigt.
AccessTheWebAsync, die den Inhalt einer Website als Zeichenfolge herunterlädt und die Länge der Zeichenfolge zurückgibt. AccessTheWebAsync verwendet eine asynchrone HttpClient-Methode, GetStringAsync(String), um den Inhalt herunterzuladen.
Nummerierte Ausgabezeilen werden über das Programm verteilt an strategischen Stellen angezeigt, die dabei helfen sollen, die Ausführung des Programms nachzuvollziehen und zu verdeutlichen, was an den markierten Punkten geschieht. Die Ausgabezeilen werden durchgehend von "ONE" (Eins) bis "SIX" (Sechs) bezeichnet. Die Bezeichnungen entsprechen der Reihenfolge, in der das Programm diese Codezeilen erreicht.
Im folgenden Code wird die Gliederung des Programms angezeigt.
Class MainWindow
Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click
' ONE
Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
' FOUR
Dim contentLength As Integer = Await getLengthTask
' SIX
ResultsTextBox.Text &=
String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength)
End Sub
Async Function AccessTheWebAsync() As Task(Of Integer)
' TWO
Dim client As HttpClient = New HttpClient()
Dim getStringTask As Task(Of String) =
client.GetStringAsync("https://msdn.microsoft.com")
' THREE
Dim urlContents As String = Await getStringTask
' FIVE
Return urlContents.Length
End Function
End Class
public partial class MainWindow : Window
{
// . . .
private async void startButton_Click(object sender, RoutedEventArgs e)
{
// ONE
Task<int> getLengthTask = AccessTheWebAsync();
// FOUR
int contentLength = await getLengthTask;
// SIX
resultsTextBox.Text +=
String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
}
async Task<int> AccessTheWebAsync()
{
// TWO
HttpClient client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://msdn.microsoft.com");
// THREE
string urlContents = await getStringTask;
// FIVE
return urlContents.Length;
}
}
An jeder der mit "ONE" bis "SIX" bezeichneten Stellen werden Informationen über den aktuellen Status des Programms angezeigt. Es wird die folgende Ausgabe generiert.
ONE: Entering startButton_Click.
Calling AccessTheWebAsync.
TWO: Entering AccessTheWebAsync.
Calling HttpClient.GetStringAsync.
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask & return a Task<int> to startButton_Click.
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
Length of the downloaded string: 33946.
Das Programm einrichten
Sie können den Code, der in diesem Thema verwendet wird, von MSDN herunterladen oder Sie können ihn selbst erstellen.
Hinweis
Zum Ausführen des Beispiels muss Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 Visual Studio Express 2013 für Windows oder .NET Framework 4.5 bzw. 4.5.1 auf dem Computer installiert sein.
Das Programm herunterladen
Sie können die Anwendung für dieses Thema von Async Sample: Control Flow in Async Programs herunterladen. Mithilfe der folgenden Schritte wird das Programm geöffnet und ausgeführt.
Entzippen Sie die heruntergeladene Datei und starten Sie anschließend Visual Studio.
Klicken Sie in der Menüleiste auf Datei, dann auf Öffnen und Projekt/Projektmappe.
Navigieren Sie zu dem Ordner, der den entzippten Beispielcode enthält, öffnen Sie die Projektmappendatei (SLN) und drücken Sie dann die F5-TASTE, um das Projekt zu erstellen und auszuführen.
Das Programm selbst erstellen
Das folgende Windows Presentation Foundation (WPF)-Projekt enthält das Codebeispiel für dieses Thema.
Um das Projekt auszuführen, führen Sie die folgenden Schritte aus:
Starten Sie Visual Studio.
Wählen Sie in der Menüleiste Datei, Neu, Projekt aus.
Das Dialogfeld Neues Projekt wird angezeigt.
Wählen Sie im Bereich Installierte Vorlagen den Eintrag Visual Basic oder Visual C# aus und wählen Sie dann in der Liste der Projekttypen WPF-Anwendung aus.
Geben Sie AsyncTracer als Namen für das Projekt ein und wählen Sie dann die Schaltfläche OK aus.
Das neue Projekt wird im Projektmappen-Explorer angezeigt.
Wählen Sie im Visual Studio Code Editor die Registerkarte MainWindow.xaml aus.
Wenn die Registerkarte nicht sichtbar ist, öffnen Sie das Kontextmenü für "MainWindow.xaml" im Projektmappen-Explorer, und wählen Sie dann Code anzeigen aus.
Ersetzen Sie den automatisch generierten Code in der XAML-Ansicht der MainWindow.xaml-Datei durch den folgenden Code.
<Window xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MainWindow" Title="Control Flow Trace" Height="350" Width="525"> <Grid> <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="221,10,0,0" VerticalAlignment="Top" Width="75"/> <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="510" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" d:LayoutOverrides="HorizontalMargin"/> </Grid> </Window>
<Window xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="AsyncTracer.MainWindow" Title="Control Flow Trace" Height="350" Width="592"> <Grid> <Button x:Name="startButton" Content="Start
" HorizontalAlignment="Left" Margin="250,10,0,0" VerticalAlignment="Top" Width="75" Height="24" Click="startButton_Click" d:LayoutOverrides="GridBox"/> <TextBox x:Name="resultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="576" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" Grid.ColumnSpan="3"/> </Grid> </Window>
Ein einfaches Fenster, das ein Textfeld und eine Schaltfläche enthält, wird in der Entwurf-Ansicht der MainWindow.xaml-Datei angezeigt.
Fügen Sie einen Verweis für System.Net.Http hinzu.
Öffnen Sie im Projektmappen-Explorer das Kontextmenü für "MainWindow.xaml.cs" oder "MainWindow.xaml.vb", und wählen Sie dann Code anzeigen aus.
Ersetzen Sie in „MainWindow.xaml.vb“ bzw. „MainWindow.xaml.cs“ den Code durch den folgenden Code.
' Add an Imports statement and a reference for System.Net.Http. Imports System.Net.Http Class MainWindow Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click ' The display lines in the example lead you through the control shifts. ResultsTextBox.Text &= "ONE: Entering StartButton_Click." & vbCrLf & " Calling AccessTheWebAsync." & vbCrLf Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync() ResultsTextBox.Text &= vbCrLf & "FOUR: Back in StartButton_Click." & vbCrLf & " Task getLengthTask is started." & vbCrLf & " About to await getLengthTask -- no caller to return to." & vbCrLf Dim contentLength As Integer = Await getLengthTask ResultsTextBox.Text &= vbCrLf & "SIX: Back in StartButton_Click." & vbCrLf & " Task getLengthTask is finished." & vbCrLf & " Result from AccessTheWebAsync is stored in contentLength." & vbCrLf & " About to display contentLength and exit." & vbCrLf ResultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength) End Sub Async Function AccessTheWebAsync() As Task(Of Integer) ResultsTextBox.Text &= vbCrLf & "TWO: Entering AccessTheWebAsync." ' Declare an HttpClient object. Dim client As HttpClient = New HttpClient() ResultsTextBox.Text &= vbCrLf & " Calling HttpClient.GetStringAsync." & vbCrLf ' GetStringAsync returns a Task(Of String). Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com") ResultsTextBox.Text &= vbCrLf & "THREE: Back in AccessTheWebAsync." & vbCrLf & " Task getStringTask is started." ' AccessTheWebAsync can continue to work until getStringTask is awaited. ResultsTextBox.Text &= vbCrLf & " About to await getStringTask & return a Task(Of Integer) to StartButton_Click." & vbCrLf ' Retrieve the website contents when task is complete. Dim urlContents As String = Await getStringTask ResultsTextBox.Text &= vbCrLf & "FIVE: Back in AccessTheWebAsync." & vbCrLf & " Task getStringTask is complete." & vbCrLf & " Processing the return statement." & vbCrLf & " Exiting from AccessTheWebAsync." & vbCrLf Return urlContents.Length End Function End Class
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 a using directive and a reference for System.Net.Http; using System.Net.Http; namespace AsyncTracer { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void startButton_Click(object sender, RoutedEventArgs e) { // The display lines in the example lead you through the control shifts. resultsTextBox.Text += "ONE: Entering startButton_Click.\r\n" + " Calling AccessTheWebAsync.\r\n"; Task<int> getLengthTask = AccessTheWebAsync(); resultsTextBox.Text += "\r\nFOUR: Back in startButton_Click.\r\n" + " Task getLengthTask is started.\r\n" + " About to await getLengthTask -- no caller to return to.\r\n"; int contentLength = await getLengthTask; resultsTextBox.Text += "\r\nSIX: Back in startButton_Click.\r\n" + " Task getLengthTask is finished.\r\n" + " Result from AccessTheWebAsync is stored in contentLength.\r\n" + " About to display contentLength and exit.\r\n"; resultsTextBox.Text += String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength); } async Task<int> AccessTheWebAsync() { resultsTextBox.Text += "\r\nTWO: Entering AccessTheWebAsync."; // Declare an HttpClient object. HttpClient client = new HttpClient(); resultsTextBox.Text += "\r\n Calling HttpClient.GetStringAsync.\r\n"; // GetStringAsync returns a Task<string>. Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com"); resultsTextBox.Text += "\r\nTHREE: Back in AccessTheWebAsync.\r\n" + " Task getStringTask is started."; // AccessTheWebAsync can continue to work until getStringTask is awaited. resultsTextBox.Text += "\r\n About to await getStringTask and return a Task<int> to startButton_Click.\r\n"; // Retrieve the website contents when task is complete. string urlContents = await getStringTask; resultsTextBox.Text += "\r\nFIVE: Back in AccessTheWebAsync." + "\r\n Task getStringTask is complete." + "\r\n Processing the return statement." + "\r\n Exiting from AccessTheWebAsync.\r\n"; return urlContents.Length; } } }
Drücken Sie die Taste F5, um das Programm auszuführen, und klicken Sie dann auf die Schaltfläche Starten.
Es sollte folgende Ausgabe angezeigt werden.
ONE: Entering startButton_Click. Calling AccessTheWebAsync. TWO: Entering AccessTheWebAsync. Calling HttpClient.GetStringAsync. THREE: Back in AccessTheWebAsync. Task getStringTask is started. About to await getStringTask & return a Task<int> to startButton_Click. FOUR: Back in startButton_Click. Task getLengthTask is started. About to await getLengthTask -- no caller to return to. FIVE: Back in AccessTheWebAsync. Task getStringTask is complete. Processing the return statement. Exiting from AccessTheWebAsync. SIX: Back in startButton_Click. Task getLengthTask is finished. Result from AccessTheWebAsync is stored in contentLength. About to display contentLength and exit. Length of the downloaded string: 33946.
Ablaufverfolgung für das Programm durchführen
Schritt EINS und ZWEI
Durch die ersten beiden Ausgabezeilen wird der Pfad verfolgt, während startButton_Click AccessTheWebAsync aufruft und AccessTheWebAsync die asynchrone HttpClient-Methode GetStringAsync(String) aufruft. Im folgenden Bild werden die Aufrufe von Methode zu Methode gezeigt.
Der Rückgabetyp sowohl von AccessTheWebAsync als auch von client.GetStringAsync ist Task. Für AccessTheWebAsync ist TResult eine ganze Zahl. Für GetStringAsync ist TResult eine Zeichenfolge. Weitere Informationen über Rückgabetypen asynchroner Methoden finden Sie unter Asynchrone Rückgabetypen (C# und Visual Basic).
Eine asynchrone Methode, die eine Aufgabe zurückgibt, gibt eine Aufgabeninstanz zurück, wenn die Steuerung wieder zum Aufrufer zurückwechselt. Die Steuerung kehrt von einer asynchronen Methode wieder zu deren Aufrufer zurück, wenn entweder ein Await- oder await-Operator in der aufgerufenen Methode auftritt oder die aufgerufene Methode beendet wird. Durch die durchgehend mit "THREE" (Drei) bis "SIX" (Sechs) bezeichneten Ausgabezeilen wird dieser Teil des Prozesses verfolgt.
Schritt DREI
In AccessTheWebAsync wird die asynchrone Methode GetStringAsync(String) aufgerufen, um den Inhalt der Zielwebseite herunterzuladen. Die Steuerung kehrt von client.GetStringAsync zu AccessTheWebAsync zurück, wenn client.GetStringAsync zurückgegeben wird.
Die client.GetStringAsync-Methode gibt eine Zeichenfolgenaufgabe zurück, die der getStringTask-Variablen in AccessTheWebAsync zugewiesen wird. Die folgende Zeile im Beispielprogramm zeigt den Aufruf von client.GetStringAsync und die Zuweisung.
Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");
Sie können sich die Aufgabe als eine Zusage von client.GetStringAsync vorstellen, schließlich eine tatsächliche Zeichenfolge zu erzeugen. In der Zwischenzeit, wenn AccessTheWebAsync auszuführende Aufgaben hat, die nicht von der zugesagten Zeichenfolge von client.GetStringAsync abhängen, können diese Aufgaben fortgesetzt werden, während client.GetStringAsync wartet. Im Beispiel bieten die folgenden, mit "THREE" bezeichneten Ausgabezeilen die Gelegenheit, um unabhängige Aufgaben auszuführen.
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask & return a Task<int> to startButton_Click.
Durch die folgende Anweisung wird die Ausführung in AccessTheWebAsync angehalten, wenn getStringTask erwartet wird.
Dim urlContents As String = Await getStringTask
string urlContents = await getStringTask;
Das folgende Bild zeigt die Ablaufsteuerung von client.GetStringAsync bis zur Zuweisung von getStringTask und von der Erstellung von getStringTask bis zur Anwendung eines await-Operators an.
Durch den await-Ausdruck wird AccessTheWebAsync angehalten, bis client.GetStringAsync zurückgegeben wird. In der Zwischenzeit kehrt die Steuerung zum Aufrufer von AccessTheWebAsync, startButton_Click, zurück.
Hinweis
In der Regel warten Sie sofort auf den Aufruf einer asynchronen Methode.Beispielsweise könnte eine der folgenden Zuweisungen den vorherigen Code ersetzen, der getStringTask erstellt und anschließend darauf wartet:
-
Visual Basic: Dim urlContents As String = Await client.GetStringAsync("https://msdn.microsoft.com")
-
C#: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");
In diesem Thema wird der await-Operator später angewendet, um die Ausgabezeilen anzupassen, die die Ablaufsteuerung durch das Programm markieren.
Schritt VIER
Der deklarierte Rückgabetyp von AccessTheWebAsync ist Task(Of Integer) in Visual Basic und Task<int> in C#. Wenn AccessTheWebAsync angehalten wird, wird daher eine Ganzzahlaufgabe an startButton_Click zurückgegeben. Sie sollten verstanden haben, dass die zurückgegebene Aufgabe nicht getStringTask ist. Die zurückgegebene Aufgabe ist eine neue Ganzzahlaufgabe, die die verbleibenden Aufgaben in der angehaltenen Methode, AccessTheWebAsync, darstellt. Die Aufgabe ist eine Zusage von AccessTheWebAsync, eine ganze Zahl zu erzeugen, wenn die Aufgabe abgeschlossen wird.
Die folgende Anweisung weist diese Aufgabe der getLengthTask-Variablen zu.
Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = AccessTheWebAsync();
Wie in AccessTheWebAsync kann startButton_Click mit Aufgaben fortsetzen, die nicht von den Ergebnissen der asynchronen Aufgabe (getLengthTask) abhängen, bis diese Aufgabe erwartet wird. Die folgenden Ausgabezeilen stellen diese Aufgaben dar.
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
Die Ausführung von startButton_Click wird angehalten, wenn getLengthTask erwartet wird. Durch die folgende Zuweisungsanweisung wird startButton_Click angehalten, bis AccessTheWebAsync abgeschlossen ist.
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
In der folgenden Abbildung veranschaulichen die Pfeile die Ablaufsteuerung vom await-Ausdruck in AccessTheWebAsync zur Zuweisung eines Werts an getLengthTask, gefolgt von normaler Verarbeitung in startButton_Click bis getLengthTask erwartet wird.
Schritt FÜNF
Wenn client.GetStringAsync signalisiert, dass es abgeschlossen ist, wird die Verarbeitung in AccessTheWebAsync aus dem Anhalten freigegeben und kann nach dem await-Ausdruck fortgesetzt werden. Die folgenden Ausgabezeilen stellen die Wiederaufnahme der Verarbeitung dar.
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
Der Operand der return-Anweisung, urlContents.Length, wird in der Aufgabe gespeichert, die AccessTheWebAsync zurückgibt. Der await-Ausdruck ruft diesen Wert von getLengthTask in startButton_Click ab.
Im folgenden Bild wird die Übertragung der Steuerung gezeigt, nachdem client.GetStringAsync (und getStringTask) abgeschlossen sind.
AccessTheWebAsync wird bis zum Abschluss ausgeführt und die Steuerung kehrt zu startButton_Click zurück, das den Abschluss erwartet.
Schritt SECHS
Wenn AccessTheWebAsync signalisiert, dass es abgeschlossen ist, kann die Verarbeitung nach der await-Anweisung in startButton_Async fortgesetzt werden. Für das Programm gibt es eigentlich nichts mehr zu tun.
Die folgenden Ausgabezeilen stellen die Wiederaufnahme der Verarbeitung in startButton_Async dar:
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
Der await-Ausdruck ruft von getLengthTask den ganzzahligen Wert ab, der der Operand der return-Anweisung in AccessTheWebAsync ist. Die folgende Anweisung weist diesen Wert der contentLength-Variablen zu.
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
Im folgenden Bild wird die Rückgabe der Steuerung von AccessTheWebAsync an startButton_Click gezeigt.
Siehe auch
Aufgaben
Exemplarische Vorgehensweise: Zugreifen auf das Web mit Async und Await (C# und Visual Basic)
Exemplarische Vorgehensweise: Verwenden des Debuggers mit Async-Methoden
Konzepte
Asynchrone Programmierung mit Async und Await (C# und Visual Basic)
Asynchrone Rückgabetypen (C# und Visual Basic)
Weitere Ressourcen
Async Sample: Control Flow in Async Programs (C# and Visual Basic)