Поток управления в асинхронных программах (C# и Visual Basic)

Можно легче создавать и поддерживать асинхронные программы с помощью ключевых слов Async и Await.Однако результаты могут удивить, если вы не понимаете, как программа работает.В этом разделе отслеживается поток управления в простой асинхронной программе для того, чтобы показать, когда управление передается от одного метода к другому, и какая информация передается каждый раз.

ПримечаниеПримечание

Ключевые слова Async и Await были введены в Visual Studio 2012.Дополнительные сведения о новых функциях в этой версии см. в разделе Новые возможности Visual Studio 2012, версия-кандидат.

Как правило, методы, которые содержат асинхронный код, помечаются модификатором Async (Visual Basic) или async (C#).В методе, помеченном модификатором async, можно использовать оператор Await (Visual Basic) или await (C#), чтобы определить, где метод приостанавливается для ожидания завершения вызванного асинхронного процесса.Для получения дополнительной информации см. Асинхронное программирование с использованием ключевых слов Async и Await (C# и Visual Basic).

В следующем примере используются асинхронные методы для загрузки содержимого конкретного веб-сайта в виде строки и отображения длины этой строки.Пример содержит следующие два метода.

  • startButton_Click, который вызывает AccessTheWebAsync и отображает результат.

  • AccessTheWebAsync, который загружает содержимое веб-сайта в виде строки и возвращает длину строки.AccessTheWebAsync использует асинхронный метод HttpClient, GetStringAsync(String) для загрузки содержимого.

Нумерованные выводимые строки играют роль стратегических точек в программе, помогая понять ход выполнения программы и те действия, которые происходят в каждой отмеченной точке.Выводимые строки отмечены от "ONE" до "SIX". Метки представляют собой порядок, в котором программа достигает эти строки кода.

В следующем коде показана структура программы.

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

Каждое из положений, обозначенных от "ONE" до "SIX", отображает сведения о текущем состоянии программы.Генерируется следующий вывод.

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.

Установка программы

Можно загрузить используемый в этом разделе код из MSDN, или же можно самостоятельно создать его самостоятельно.

ПримечаниеПримечание

Запуск образца необходимо иметь Visual Studio 2012, Visual Studio Express 2012 или .NET Framework 4,5, на компьютере.

Hh873191.collapse_all(ru-ru,VS.110).gifЗагрузка программы

Можно загрузить приложение для этого раздела из Асинхронный пример: поток управления в асинхронных программах.В следующих шагах программа открывается и запускается.

  1. Распакуйте загруженный файл, а затем запустите Visual Studio 2012.

  2. В строке меню выберите Файл, Открыть, Проект/Решение.

  3. Перейдите к папке, содержащей распакованный образец кода, откройте файл решения (sln), а затем нажмите клавишу F5 для построения и выполнения проекта.

Hh873191.collapse_all(ru-ru,VS.110).gifПостроение программы самостоятельно

Следующий проект Windows Presentation Foundation (WPF) содержит пример кода для данного раздела.

Чтобы запустить этот проект, выполните указанные ниже действия:

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

  2. В строке меню выберите Файл, Создать, Проект.

    Откроется диалоговое окно Новый проект.

  3. На панели Установленные шаблоны выберите Visual Basic или Visual C#, а затем выберите Приложение WPF из списка типов проектов.

  4. Введите в качестве имени проекта AsyncTracer, а затем нажмите кнопку ОК.

    В обозревателе решений появится новый проект.

  5. Выберите в редакторе кода Visual Studio вкладку MainWindow.xaml.

    Если вкладка не отображается, откройте контекстное меню для MainWindow.xaml в Обозреватель решений, а затем выберите Перейти к коду.

  6. В представлении XAML замените автоматически созданный код на следующий код.

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

    Простое окно, содержащее текстовое поле и кнопку, отображается в представлении Конструктор MainWindow.xaml.

  7. Добавьте ссылку на System.Net.Http.

  8. В Обозревателе решений откройте контекстное меню для MainWindow.xaml.vb или MainWindow.xaml.cs, а затем выберите Код.

  9. В файле MainWindow.xaml.vb или MainWindow.xaml.cs замените код на следующий.

    ' 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. Нажмите клавишу F5, чтобы запустить программу, а затем нажмите кнопку Start.

    Появятся следующие выходные данные.

    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.
    

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

Hh873191.collapse_all(ru-ru,VS.110).gifШаги ONE и TWO

Первые две выходные строки трассируют путь того, как startButton_Click вызывает AccessTheWebAsync, а AccessTheWebAsync вызывает асинхронный HttpClient метод GetStringAsync(String).Следующее изображение показывает вызовы методов из методов.

Шаги ОДИН и ДВА

Тип возвращаемого значения обоих AccessTheWebAsync и client.GetStringAsync — Task<TResult>.Для AccessTheWebAsync TResult — целое число.Для GetStringAsync TResult — строка.Дополнительные сведения о возвращаемых типах асинхронных методов см. в разделе Асинхронные типы возвращаемых значений (C# и Visual Basic).

Асинхронный метод, возвращающий задание, возвращает экземпляр задания, когда поток управления возвращается к вызывающему объекту.Управление передается от асинхронного метода к вызывающему, когда в вызывающем методе обнаружен оператор Await или await, или вызванный метод завершается.Выходные строки, которые помечены от "THREE" до "SIX", трассируют эту часть процесса.

Hh873191.collapse_all(ru-ru,VS.110).gifШаг THREE

В AccessTheWebAsync, асинхронный вызов метода GetStringAsync(String), чтобы загрузить содержимое веб-страницы целевого объекта.Управление передается от client.GetStringAsync к AccessTheWebAsync при возврате client.GetStringAsync.

Метод client.GetStringAsync возвращает задачу строки, которая назначена переменной getStringTask в AccessTheWebAsync.Следующая строка в примере программы показывает вызов client.GetStringAsync и присваивание.

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

Можно представить себе задачу как обещание client.GetStringAsync создать фактическую строку.Тем временем, если AccessTheWebAsync имеет задания, которые не зависят от обещанной строки из client.GetStringAsync, он может продолжать выполнять работу, пока client.GetStringAsync ожидает.В этом примере линии вывода, которые обозначены "THREE", представляют возможность сделать независимую работу.

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

Следующий оператор приостанавливает ход AccessTheWebAsync при ожидании getStringTask.

Dim urlContents As String = Await getStringTask
string urlContents = await getStringTask;

Следующее изображение показывает поток управления из client.GetStringAsync к присваиванию getStringTask, и от создания getStringTask к применению оператора await.

Шаг ТРИ

Выражение await приостанавливает AccessTheWebAsync до тех пор, пока не завершится client.GetStringAsync.Тем временем, управление возвращается к вызывающему объекту метода AccessTheWebAsync, startButton_Click.

ПримечаниеПримечание

Как правило, вызов асинхронного метода ожидается немедленно.Например, одно из следующих присваиваний может заменить предыдущий код, который создает, а затем ожидает getStringTask:

  • В Visual Basic: Dim urlContents As String = Await client.GetStringAsync("http://msdn.microsoft.com")

  • В C#: string urlContents = await client.GetStringAsync("http://msdn.microsoft.com");

В этом разделе, оператор await применяется далее так, чтобы вместить выходные строки, которые помечают поток управления внутри программы.

Hh873191.collapse_all(ru-ru,VS.110).gifШаг FOUR

Объявленный тип возвращаемого значения AccessTheWebAsync — Task(Of Integer) в Visual Basic и Task<int> — в C#.Поэтому при приостановке AccessTheWebAsync, он возвращает задачу типа целого числа в startButton_Click.Необходимо понимать, что, возвращаемая задача не getStringTask.Возвращаемая задача — новая задача типа целого числа, представляющая то, что еще нужно сделать в приостановленном методе, AccessTheWebAsync.Задача — обещание AccessTheWebAsync создать целое число, когда задача будет завершена.

Следующий оператор присвоит эту задачу переменной getLengthTask.

Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = AccessTheWebAsync();

Например, в AccessTheWebAsync, startButton_Click может продолжать работу, которая не зависит от результатов асинхронной задачи (getLengthTask) до тех пор, пока задача не будет ожидаемой.Следующие выходные строки представляют эту работу.

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

Ход startButton_Click приостанавливается при ожидании getLengthTask.Следующий оператор присваивания startButton_Click приостанавливается до тех пор, пока AccessTheWebAsync не будет завершен.

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

На следующем рисунке стрелки указывают поток управления из выражения await в AccessTheWebAsync к присваиванию значения getLengthTask, которое продолжается нормальной обработкой в startButton_Click до тех пор, пока не ожидается getLengthTask.

Шаг ЧЕТЫРЕ

Hh873191.collapse_all(ru-ru,VS.110).gifШаг FIVE

Когда client.GetStringAsync сигнализирует, что он завершен, обработка в AccessTheWebAsync освобождается от приостановки и может продолжаться после оператора await.Следующие выходные строки представляют продолжение обработки.

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

Операнд выражения return, urlContents.Length, сохраняется в задаче, которую возвращает AccessTheWebAsync.Выражение await извлекает это значение из getLengthTask в startButton_Click.

Следующее изображение показывает передачу управления после завершения client.GetStringAsync (и getStringTask).

Шаг ПЯТЬ

AccessTheWebAsync выполняется до завершения, и управление возвращается к startButton_Click, который ожидает завершения.

Hh873191.collapse_all(ru-ru,VS.110).gifШаг SIX

Когда AccessTheWebAsync сигнализирует, что он завершен, обработка может продолжаться после выражения await в startButton_Async.В действительности, программе больше нечего делать.

Следующие выходные строки представляют продолжение обработки в 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.Следующие выражение присваивает это значение переменной contentLength.

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

Следующее изображение показывает возврат управления от AccessTheWebAsync к startButton_Click.

Шаг ШЕСТЬ

См. также

Задачи

Пошаговое руководство. Получение доступа к Интернету с помощью модификатора Async и оператора Await (C# и Visual Basic)

Пошаговое руководство. Использование отладчика с асинхронными методами

Основные понятия

Асинхронное программирование с использованием ключевых слов Async и Await (C# и Visual Basic)

Асинхронные типы возвращаемых значений (C# и Visual Basic)

Другие ресурсы

Асинхронный пример: поток управления в асинхронных программах (C# и Visual Basic)