Поток управления в асинхронных программах (C#)Control flow in async programs (C#)

Можно намного проще создавать и обслуживать асинхронные программы с помощью ключевых слов async и await.You can write and maintain asynchronous programs more easily by using the async and await keywords. Однако при непонимании механизма работы асинхронной программы результаты могут удивить.However, the results might surprise you if you don't understand how your program operates. В этом разделе выполняется трассировка потока управления с помощью простой асинхронной программы, чтобы продемонстрировать переход потока управления от одного метода к другому, включая данные, передаваемые в каждом случае.This topic traces the flow of control through a simple async program to show you when control moves from one method to another and what information is transferred each time.

Как правило, вы помечаете методы, содержащие асинхронный код, с помощью модификатора async (C#).In general, you mark methods that contain asynchronous code with the async (C#) modifier. В методе, помеченном с помощью модификатора async, можно использовать оператор await (C#), чтобы указать место приостановки метода для ожидания завершения вызванного асинхронного процесса.In a method that's marked with an async modifier, you can use an await (C#) operator to specify where the method pauses to wait for a called asynchronous process to complete. Дополнительные сведения см. в разделе Асинхронное программирование с использованием ключевых слов Async и Await (C#).For more information, see Asynchronous Programming with async and await (C#).

В следующем примере асинхронные методы используются для загрузки содержимого указанного веб-сайта в виде строки и отображения длины строки.The following example uses async methods to download the contents of a specified website as a string and to display the length of the string. Пример содержит следующие два метода:The example contains the following two methods.

  • startButton_Click, который вызывает метод AccessTheWebAsync и выводит результат;startButton_Click, which calls AccessTheWebAsync and displays the result.

  • AccessTheWebAsync, который загружает содержимое веб-сайта в виде строки и возвращает длину строки.AccessTheWebAsync, which downloads the contents of a website as a string and returns the length of the string. AccessTheWebAsync использует для загрузки содержимого асинхронный метод HttpClient GetStringAsync(String).AccessTheWebAsync uses an asynchronous HttpClient method, GetStringAsync(String), to download the contents.

Выводимые строки помечены номерами на стратегических этапах программы, чтобы помочь вам понять, как работает программа и что происходит на каждом отмеченном этапе.Numbered display lines appear at strategic points throughout the program to help you understand how the program runs and to explain what happens at each point that is marked. Выводимые строки обозначены номерами от ONE (один) до SIX (шесть).The display lines are labeled "ONE" through "SIX." Метки представляют порядок, в котором программа достигает эти строки кода.The labels represent the order in which the program reaches these lines of code.

В следующем примере кода показана структура программы.The following code shows an outline of the program.

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 +=
            $"\r\nLength of the downloaded string: {contentLength}.\r\n";
    }

    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;
    }
}

Каждое из расположений, обозначенное от одного до шести, отображает сведения о текущем состоянии программы.Each of the labeled locations, "ONE" through "SIX," displays information about the current state of the program. Выводятся следующие результаты.The following output is produced:

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.

Настройка программыSet up the program

Можно загрузить используемый в этом разделе код из MSDN, или же можно создать его самостоятельно.You can download the code that this topic uses from MSDN, or you can build it yourself.

Примечание

Для выполнения этого примера на компьютере должны быть установлены Visual Studio 2012 или более поздней версии и .NET Framework 4.5 или более поздней версии.To run the example, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

Скачивание программыDownload the program

Вы можете скачать приложение для этого раздела на странице примеров Пример Async: поток управления в асинхронных программах.You can download the application for this topic from Async Sample: Control Flow in Async Programs. Следующие шаги описывают процесс открытия и запуска программы.The following steps open and run the program.

  1. Распакуйте загруженный файл, а затем запустите Visual Studio.Unzip the downloaded file, and then start Visual Studio.

  2. В строке меню выберите Файл > Открыть > Решение или проект.On the menu bar, choose File > Open > Project/Solution.

  3. Перейдите к папке, содержащей распакованный пример кода, откройте файл решения (SLN), а затем нажмите клавишу F5 для сборки и выполнения проекта.Navigate to the folder that holds the unzipped sample code, open the solution (.sln) file, and then choose the F5 key to build and run the project.

Самостоятельное создание программыCreate the program Yourself

Следующий проект Windows Presentation Foundation (WPF) содержит примеры кода для этого раздела.The following Windows Presentation Foundation (WPF) project contains the code example for this topic.

Чтобы запустить проект, выполните следующие действия.To run the project, perform the following steps:

  1. Запустите Visual Studio.Start Visual Studio.

  2. В строке меню выберите Файл > Создать > Проект.On the menu bar, choose File > New > Project.

    Откроется диалоговое окно Новый проект .The New Project dialog box opens.

  3. Выберите категории Установленные > Visual C# > Windows Desktop, а затем выберите Приложение WPF в списке шаблонов проектов.Choose the Installed > Visual C# > Windows Desktop category, and then choose WPF App from the list of project templates.

  4. Введите AsyncTracer в качестве имени проекта и нажмите кнопку ОК.Enter AsyncTracer as the name of the project, and then choose the OK button.

    В обозревателе решений появится новый проект.The new project appears in Solution Explorer.

  5. В редакторе кода Visual Studio перейдите на вкладку MainWindow.xaml .In the Visual Studio Code Editor, choose the MainWindow.xaml tab.

    Если вкладка не отображается, откройте контекстное меню для MainWindow.xaml в обозревателе решений и выберите пункт Просмотреть код.If the tab isn’t visible, open the shortcut menu for MainWindow.xaml in Solution Explorer, and then choose View Code.

  6. Замените код в представлении XAML файла MainWindow.xaml на следующий.In the XAML view of MainWindow.xaml, replace the code with the following code.

    <Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://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>
    

    В представлении Конструктор файла MainWindow.xaml появится простое окно, содержащее кнопку и текстовое поле.A simple window that contains a text box and a button appears in the Design view of MainWindow.xaml.

  7. Добавьте ссылку для System.Net.Http.Add a reference for System.Net.Http.

  8. В обозревателе решений откройте контекстное меню для MainWindow.xaml.cs и выберите пункт Просмотреть код.In Solution Explorer, open the shortcut menu for MainWindow.xaml.cs, and then choose View Code.

  9. В MainWindow.xaml.cs замените код на следующий.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 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 +=
                    $"\r\nLength of the downloaded string: {contentLength}.\r\n";
            }
    
            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. Нажмите клавишу F5, чтобы запустить программу, а затем нажмите кнопку Запуск.Choose the F5 key to run the program, and then choose the Start button.

    Появится следующий результат:The following output appears:

    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.
    

Трассировка программыTrace the program

Шаги ОДИН и ДВАSteps ONE and TWO

В первых двух строках прослеживается путь по мере того, как метод startButton_Click вызывает AccessTheWebAsync, а AccessTheWebAsync вызывает асинхронный метод HttpClient GetStringAsync(String).The first two display lines trace the path as startButton_Click calls AccessTheWebAsync, and AccessTheWebAsync calls the asynchronous HttpClient method GetStringAsync(String). Ниже показаны вызовы из метода в метод.The following image outlines the calls from method to method.

Шаги ONE (один) и TWO (два)Steps ONE and TWO

Типом возвращаемого значения и для AccessTheWebAsync, и для client.GetStringAsync является Task<TResult>.The return type of both AccessTheWebAsync and client.GetStringAsync is Task<TResult>. Для AccessTheWebAsync значение TResult является целым числом.For AccessTheWebAsync, TResult is an integer. Для GetStringAsync значение TResult является строкой.For GetStringAsync, TResult is a string. Дополнительные сведения о возвращаемых типах асинхронных методов см. в разделе Асинхронные типы возвращаемых значений (C#).For more information about async method return types, see Async Return Types (C#).

Асинхронный метод, возвращающий задачи, возвращает экземпляр задачи, когда контроль управления возвращается к вызывающему объекту.A task-returning async method returns a task instance when control shifts back to the caller. Управление передается от асинхронного метода его вызывающему методу, когда в вызванном методе обнаруживается оператор await или когда вызванный метод завершается.Control returns from an async method to its caller either when an await operator is encountered in the called method or when the called method ends. Отображаемые строки, которые помечены от трёх до шести, отслеживают эту часть процесса.The display lines that are labeled "THREE" through "SIX" trace this part of the process.

Шаг ТРИStep THREE

В AccessTheWebAsync для загрузки содержимого целевой веб-страницы вызывается асинхронный метод GetStringAsync(String).In AccessTheWebAsync, the asynchronous method GetStringAsync(String) is called to download the contents of the target webpage. Управление передается от client.GetStringAsync методу AccessTheWebAsync при возвращении результатов методом client.GetStringAsync.Control returns from client.GetStringAsync to AccessTheWebAsync when client.GetStringAsync returns.

Метод client.GetStringAsync возвращает задачу строки, назначенной переменной getStringTask в AccessTheWebAsync.The client.GetStringAsync method returns a task of string that’s assigned to the getStringTask variable in AccessTheWebAsync. Следующая строка в примере программы демонстрирует вызов client.GetStringAsync и назначение.The following line in the example program shows the call to client.GetStringAsync and the assignment.

Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");

Можно представить себе задачу как обещание client.GetStringAsync создать в конечном итоге фактическую строку.You can think of the task as a promise by client.GetStringAsync to produce an actual string eventually. В то же время, если у AccessTheWebAsync есть работа, не зависящая от обещанной строки, от client.GetStringAsync, эта работа будет продолжена во время ожидания client.GetStringAsync.In the meantime, if AccessTheWebAsync has work to do that doesn't depend on the promised string from client.GetStringAsync, that work can continue while client.GetStringAsync waits. В этом примере следующие строки вывода, которые обозначены как "THREE", представляют возможность сделать независимую работу.In the example, the following lines of output, which are labeled "THREE," represent the opportunity to do independent work

THREE: Back in AccessTheWebAsync.
           Task getStringTask is started.
           About to await getStringTask & return a Task<int> to startButton_Click.

Следующая инструкция приостанавливает ход выполнения в AccessTheWebAsync при ожидании getStringTask.The following statement suspends progress in AccessTheWebAsync when getStringTask is awaited.

string urlContents = await getStringTask;

На следующем рисунке показан поток управления из client.GetStringAsync к назначению getStringTask и из создания getStringTask к применению оператора await.The following image shows the flow of control from client.GetStringAsync to the assignment to getStringTask and from the creation of getStringTask to the application of an await operator.

Шаг THREE (три)Step THREE

Выражение await приостанавливает AccessTheWebAsync до возвращения результатов client.GetStringAsync.The await expression suspends AccessTheWebAsync until client.GetStringAsync returns. На это время управление возвращается вызывающему объекту метода AccessTheWebAsync, startButton_Click.In the meantime, control returns to the caller of AccessTheWebAsync, startButton_Click.

Примечание

Как правило, ожидание вызова асинхронного метода выполняется немедленно.Typically, you await the call to an asynchronous method immediately. Например, следующее присвоение может заменить предыдущий код, который создает, а затем ожидает getStringTask: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");For example, the following assignment could replace the previous code that creates and then awaits getStringTask: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");

В этом разделе оператор await применяется позже для размещения строк, которые отмечают поток управления в программе.In this topic, the await operator is applied later to accommodate the output lines that mark the flow of control through the program.

Шаг ЧЕТЫРЕStep FOUR

Объявленный тип возвращаемого значения AccessTheWebAsync — Task<int>.The declared return type of AccessTheWebAsync is Task<int>. Таким образом, когда выполнение AccessTheWebAsync приостанавливается, он возвращает задачу целого числа в startButton_Click.Therefore, when AccessTheWebAsync is suspended, it returns a task of integer to startButton_Click. Следует понимать, что возвращаемая задача — не getStringTask.You should understand that the returned task isn’t getStringTask. Возвращаемая задача — это новая задача целого числа, представляющая оставшуюся работу в приостановленном методе AccessTheWebAsync.The returned task is a new task of integer that represents what remains to be done in the suspended method, AccessTheWebAsync. Задача является обещанием AccessTheWebAsync создать фактическое целое число после завершения задачи.The task is a promise from AccessTheWebAsync to produce an integer when the task is complete.

Следующий оператор назначает эту задачу переменной getLengthTask.The following statement assigns this task to the getLengthTask variable.

Task<int> getLengthTask = AccessTheWebAsync();

Как и в AccessTheWebAsync, startButton_Click может продолжать работу, которая не зависит от результатов асинхронной задачи (getLengthTask), во время ожидания задачи.As in AccessTheWebAsync, startButton_Click can continue with work that doesn’t depend on the results of the asynchronous task (getLengthTask) until the task is awaited. Следующие выходные строки представляют такую работу.The following output lines represent that work.

FOUR:  Back in startButton_Click.
           Task getLengthTask is started.
           About to await getLengthTask -- no caller to return to.

Выполнение в startButton_Click приостанавливается при ожидании getLengthTask.Progress in startButton_Click is suspended when getLengthTask is awaited. Следующая инструкция назначения приостанавливает startButton_Click до завершения AccessTheWebAsync.The following assignment statement suspends startButton_Click until AccessTheWebAsync is complete.

int contentLength = await getLengthTask;

На следующем рисунке стрелками показан поток управления из выражения await в AccessTheWebAsync к назначению значения getLengthTask, за которым следует обычная обработка в методе startButton_Click до ожидания getLengthTask.In the following illustration, the arrows show the flow of control from the await expression in AccessTheWebAsync to the assignment of a value to getLengthTask, followed by normal processing in startButton_Click until getLengthTask is awaited.

Шаг FOUR (четыре)Step FOUR

Шаг ПЯТЬStep FIVE

Когда client.GetStringAsync уведомляет о завершении, обработка в AccessTheWebAsync возобновляется и может продолжаться после оператора await.When client.GetStringAsync signals that it’s complete, processing in AccessTheWebAsync is released from suspension and can continue past the await statement. Приведенные ниже строки выходных данных представляют возобновление обработки.The following lines of output represent the resumption of processing.

FIVE:  Back in AccessTheWebAsync.
           Task getStringTask is complete.
           Processing the return statement.
           Exiting from AccessTheWebAsync.

Операнд оператора return, urlContents.Length, хранится в задаче, возвращаемой AccessTheWebAsync.The operand of the return statement, urlContents.Length, is stored in the task that AccessTheWebAsync returns. Выражение await получает это значение из getLengthTask в startButton_Click.The await expression retrieves that value from getLengthTask in startButton_Click.

На следующем рисунке показана передача управления после завершения client.GetStringAsyncgetStringTask).The following image shows the transfer of control after client.GetStringAsync (and getStringTask) are complete.

Шаг FIVE (пять)Step FIVE

AccessTheWebAsync выполняется до завершения, и управление возвращается к startButton_Click, который ожидает завершения.AccessTheWebAsync runs to completion, and control returns to startButton_Click, which is awaiting the completion.

Шаг ШЕСТЬStep SIX

Когда AccessTheWebAsync уведомляет о завершении, обработка может продолжаться после оператора await в startButton_Async.When AccessTheWebAsync signals that it’s complete, processing can continue past the await statement in startButton_Async. Фактически, на этом работа программы завершается.In fact, the program has nothing more to do.

Приведенные ниже строки выходных данных представляют возобновление обработки в startButton_Async:The following lines of output represent the resumption of processing in startButton_Async:

SIX:   Back in startButton_Click.
           Task getLengthTask is finished.
           Result from AccessTheWebAsync is stored in contentLength.
           About to display contentLength and exit.

Выражение await получает из getLengthTask целое значение, представляющее операнд оператора return в AccessTheWebAsync.The await expression retrieves from getLengthTask the integer value that’s the operand of the return statement in AccessTheWebAsync. Следующий оператор назначает это значение переменной contentLength.The following statement assigns that value to the contentLength variable.

int contentLength = await getLengthTask;

На следующем рисунке показано возвращение управления от AccessTheWebAsync к startButton_Click.The following image shows the return of control from AccessTheWebAsync to startButton_Click.

Шаг SIX (шесть)Step SIX

См. такжеSee also