Przepływ sterowania w aplikacjach asynchronicznych (C# i Visual Basic)
Można pisać i łatwiej utrzymać asynchroniczne programy za pomocą słów kluczowych Async i Await.Jednak wyniki mogą Cię zaskoczyć, jeśli nie rozumiesz sposobu działania programu.W tym temacie omówiono przepływ sterowania za pośrednictwem prostego programu asynchronicznego, aby pokazać, kiedy sterowania przechodzi od jednej metody do innej i jakie informacje są przesyłane za każdym razem.
[!UWAGA]
Słowa kluczowe Async i Await wprowadzono w programie Visual Studio 2012.
Ogólnie rzecz biorąc oznacz metody, które zawierają kod asynchroniczny, modyfikatorem Async (Visual Basic) lub async (C#).W metodzie, która jest oznaczona modyfikatorem asynchronicznym, można użyć operatora Await (Visual Basic) lub await (C#), aby określić, gdzie metoda wstrzymuje się, aby czekać na zakończenie wywołanego asynchronicznego procesu.Aby uzyskać więcej informacji, zobacz Programowanie asynchroniczne z Async i Await (C# i Visual Basic).
Poniższy przykład używa metody asynchronicznej, aby pobrać zawartość określonej witryny sieci web jako ciąg i wyświetlić długość ciągu.Przykład zawiera dwie poniższe metody.
startButton_Click, która wywołuje metodę AccessTheWebAsync i wyświetla wynik.
AccessTheWebAsync, który pobiera zawartość witryny sieci Web jako ciąg i zwraca długość ciągu.AccessTheWebAsync używa asynchronicznej metody HttpClient, GetStringAsync(String), aby pobrać zawartość.
Numerowanie wyświetlanych wierszy pojawiających się w strategiczny punktach w całym programie, aby pomóc Ci zrozumieć, jak działa program i wyjaśnić, co się dzieje w każdym punkcie, który jest oznaczony.Wyświetlane wiersze są oznaczone etykietami od ONE do SIX. Etykiety reprezentują kolejność, w jakiej program osiąga te wiersze kodu.
Poniższy kod przedstawia zarys programu.
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;
}
}
Każda lokalizacja z etykietą, od „ONE” to „SIX”, wyświetla informacje dotyczące bieżącego stanu programu.Generowane są poniższe dane wyjściowe.
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.
Konfigurowanie programu
Można ściągnąć kod, którego
[!UWAGA]
Aby uruchomić przykład, na komputerze musisz mieć zainstalowane programy Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012, Visual Studio Express 2013 for Windows lub .NET Framework w wersji 4.5 lub 4.5.1.
Pobierz program
Można ściągnąć aplikację dotyczącą tego tematu z Async Sample: Control Flow in Async Programs.Poniższe kroki pozwalają otworzyć i uruchomić program.
Rozpakuj pobrany plik, a następnie uruchom Visual Studio.
Na pasku menu wybierz kolejno opcje Plik, Otwórz i Projekt/rozwiązanie.
Przejdź do folderu, który posiada rozpakowany kod przykładu, otwórz plik rozwiązania (.sln) a następnie wybierz klawisz F5 w celu przeprowadzenia kompilacji i uruchomienia projektu.
Sam zbuduj program
Poniższy projekt Windows Presentation Foundation (WPF) zawiera przykład kodu dla tego tematu.
Aby uruchomić projekt, należy wykonać następujące czynności:
Uruchom program Visual Studio.
W pasku menu wybierz Plik, Nowy, Projekt.
Zostanie otwarte okno dialogowe Nowy projekt.
W kategorii Zainstalowane szablony wybierz Visual Basic lub Visual C# a następnie wybierz Aplikacja WPF z listy typów projektów.
Wprowadź AsyncTracer jako nazwę projektu, a następnie naciśnij przycisk OK.
W Eksploratorze rozwiązań pojawi się nowy projekt.
W Edytorze koloru programu Visual Studio wybierz kartę MainWindow.xaml.
Jeśli karta nie jest widoczna, otwórz menu skrótów dla pliku MainWindow.xaml w Eksploratorze rozwiązań, a następnie wybierz polecenie Wyświetl kod.
W widoku XAML pliku MainWindow.xaml, zastąp kod następującym kodem.
<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>
Proste okno zawierające pole tekstowe i przycisk pojawia się w oknie Projekt MainWindow.xaml.
Dodaj odwołanie do System.Net.Http.
W Eksploratorze rozwiązań otwórz menu skrótów dla MainWindow.xaml.vb lub MainWindow.xaml.cs, a następnie wybierz polecenie Wyświetl kod.
W MainWindow.xaml.vb lub MainWindow.xaml.cs, zastąp kod następującym kodem.
' 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; } } }
Wybierz klawisz F5, aby uruchomić program, a następnie wybierz przycisk Start.
Powinny zostać wyświetlone poniższe dane wyjściowe.
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.
Śledzenie programu
Kroki 1 i 2
Pierwsze dwa wyświetlane wiersze śledzą ścieżkę jako wywołania metody startButton_ClickAccessTheWebAsync, a metoda AccessTheWebAsync wywołuje asynchroniczną metodę HttpClientGetStringAsync(String).Poniższa ilustracja przedstawia w sposób szkicowy wywołania z metody do metody.
Zwracanym typem obu metod AccessTheWebAsync i client.GetStringAsync jest Task.Dla funkcji AccessTheWebAsync atrybut TResult jest liczbą całkowitą.Dla GetStringAsync TResult jest ciągiem.Aby uzyskać więcej informacji na temat typów zwracanych w metodach asynchronicznych, zobacz Asynchroniczne typy zwracane (C# i Visual Basic).
Asynchroniczna metoda zwracania zadania zwraca wystąpienie zadania jeśli kontrola przechodzi do obiektu wywołującego.Formant powraca z metody asynchronicznej do obiektu wywołującego, gdy operator Await lub await zostanie napotkany w metodzie wywoływanej, lub gdy metoda wywoływana się zakończy.Wyświetlane wiersze, które są oznaczone etykietami od THREE do SIX, śledzą tę część procesu.
Krok 3
W programie AccessTheWebAsync metoda asynchroniczna GetStringAsync(String) jest wywoływana w celu pobrania zawartości docelowej strony sieci Web.Formant powraca z client.GetStringAsync do AccessTheWebAsync po powrocie client.GetStringAsync.
Metoda client.GetStringAsync zwraca zadanie ciągu, który jest przypisany do zmiennej getStringTask w metodzie AccessTheWebAsync.Poniższy wiersz w programie przykładowym pokazuje wywołanie metody client.GetStringAsync i przypisanie.
Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");
Można traktować zadanie jako zapowiedź client.GetStringAsync ostatecznego utworzenia rzeczywistego ciągu.W międzyczasie, jeśli AccessTheWebAsync ma robić prace, które nie są zależne od uzgodnionego ciągu z client.GetStringAsync, że można kontynuować pracę podczas client.GetStringAsync czeka.W przykładzie następujące wiersze danych wyjściowych, które są oznaczone jako "Trzy", reprezentują okazję do samodzielnej pracy
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask & return a Task<int> to startButton_Click.
Poniższa instrukcja wstrzymuje metodę AccessTheWebAsync, gdy jest oczekiwane zakończenie metody getStringTask.
Dim urlContents As String = Await getStringTask
string urlContents = await getStringTask;
Poniższa ilustracja przedstawia przepływ sterowania od metody client.GetStringAsync do przypisania do metody getStringTask i od utworzenia metody getStringTask do zastosowania operatora await.
Wyrażenie await wstrzymuje metodę AccessTheWebAsync, dopóki metoda client.GetStringAsync nie zwróci wartości.W międzyczasie formant powraca do obiektu wywołującego AccessTheWebAsync, startButton_Click.
[!UWAGA]
Zazwyczaj można oczekiwać natychmiastowego wywołania metody asynchronicznej.Na przykład jedno z następujących przypisań może zastąpić poprzedni kod, który tworzy, a następnie czeka na funkcję getStringTask:
Język Visual Basic: Dim urlContents As String = Await client.GetStringAsync("https://msdn.microsoft.com")
C#: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");
W tym temacie operator oczekiwania jest stosowany później, aby pomieścić linie wyjściowych, oznaczające przepływ sterowania za pośrednictwem programu.
Krok CZWARTY
Deklarowanym typem zwracanym metody AccessTheWebAsync jest Task(Of Integer) w języku Visual Basic i Task<int> w języku C#.Jeśli więc metoda AccessTheWebAsync zostanie wstrzymana, zwraca zadanie liczby całkowitej do metody startButton_Click.Należy zrozumieć, że zwracane zadanie nie jest getStringTask.Zwracane zadanie jest nowym zadaniem liczby całkowitej określającej, co jeszcze pozostało do zrobienia we wstrzymanej metodzie, AccessTheWebAsync.Zadanie jest obietnicą ze strony metody AccessTheWebAsync, że na zakończenie zadania wygeneruje jakąś liczbę całkowitą.
Poniższa instrukcja przypisuje to zadanie do zmiennej getLengthTask.
Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = AccessTheWebAsync();
Podobnie jak w przypadku AccessTheWebAsync, startButton_Click może kontynuować pracę, która nie zależy od wyników zadania asynchronicznego (getLengthTask) dopóki trwa oczekiwanie na zadanie.Poniższe wiersze danych wyjściowych przedstawiają tę pracę.
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
Postęp w startButton_Click jest zawieszony gdy getLengthTask jest oczekiwany.Poniższa instrukcja przypisania wstrzymuje metodę startButton_Click do czasu zakończenia metody AccessTheWebAsync.
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
Na poniższej ilustracji, strzałki pokazują przepływ sterowania z wyrażenia oczekującego w AccessTheWebAsync do przypisania wartości do getLengthTask, a następnie normalne przetwarzanie w startButton_Click do getLengthTask jest oczekiwane.
Krok PIĄTY
Gdy client.GetStringAsync sygnalizuje zakończenie, przetwarzanie w AccessTheWebAsync jest zwalniane z zawieszenia i można je kontynuować po instrukcji czekania.Poniższe wiersze danych wyjściowych przedstawiają wznowienie przetwarzania.
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
Argument instrukcji return, urlContents.Length, jest przechowywany w zadaniu, które zwraca metoda AccessTheWebAsync.Wyrażenie await pobiera tę wartość ze zmiennej getLengthTask w metodzie startButton_Click.
Poniższa ilustracja przedstawia przekazanie sterowania po ukończeniu metody client.GetStringAsync (i getStringTask).
AccessTheWebAsync prowadzi do ukończenia, a formant powraca do startButton_Click, która oczekuje na zakończenie.
Krok SZÓSTY
Gdy AccessTheWebAsync sygnalizuje zakończenie, przetwarzanie można kontynuować po instrukcji czekania w startButton_Async.W rzeczywistości program nie ma nic więcej do zrobienia.
Następujące wiersze danych wyjściowych przedstawiają wznowienie przetwarzania w metodzie startButton_Async:
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
Wyrażenie await pobiera ze zmiennej getLengthTask wartość całkowitą, która jest argumentem instrukcji return w metodzie AccessTheWebAsync.Poniższa instrukcja przypisuje wartość do zmiennej contentLength.
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
Poniższa ilustracja przedstawia powrót sterowania z metody AccessTheWebAsync do metody startButton_Click.
Zobacz też
Zadania
Wskazówki: uzyskiwanie dostępu do sieci za pomocą Async i Await (C# i Visual Basic)
Wskazówki: Korzystanie z debugera i metod asynchronicznych
Koncepcje
Programowanie asynchroniczne z Async i Await (C# i Visual Basic)
Asynchroniczne typy zwracane (C# i Visual Basic)
Inne zasoby
Próbka asynchroniczna: Przepływ sterowania w programach Async (C# i Visual Basic)