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.

  1. Rozpakuj pobrany plik, a następnie uruchom Visual Studio.

  2. Na pasku menu wybierz kolejno opcje Plik, Otwórz i Projekt/rozwiązanie.

  3. 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:

  1. Uruchom program Visual Studio.

  2. W pasku menu wybierz Plik, Nowy, Projekt.

    Zostanie otwarte okno dialogowe Nowy projekt.

  3. W kategorii Zainstalowane szablony wybierz Visual Basic lub Visual C# a następnie wybierz Aplikacja WPF z listy typów projektów.

  4. Wprowadź AsyncTracer jako nazwę projektu, a następnie naciśnij przycisk OK.

    W Eksploratorze rozwiązań pojawi się nowy projekt.

  5. 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.

  6. 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&#xa;" 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.

  7. Dodaj odwołanie do System.Net.Http.

  8. 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.

  9. 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;
            }
        }
    }
    
  10. 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.

Kroki 1 i 2

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.

Krok 3

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 4

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).

Krok 5

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.

Krok 6

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)