Поделиться через


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

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

Примечание

Ключевые слова Async и Await были введены в 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) для загрузки содержимого.

Нумерованные выводимые строки играют роль стратегических точек в программе, помогая понять ход выполнения программы и те действия, которые происходят в каждой отмеченной точке. Выводимые строки отмечены от "ОДИН" до "ШЕСТЬ". Метки представляют собой порядок, в котором программа достигает эти строки кода.

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

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 2013, Visual Studio Express 2012, Visual Studio Express 2013 для Windows, или .NET Framework 4.5 или 4.5.1.

Загрузка программы

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

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

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

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

Самостоятельное построение программы

Следующий проект 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 MainWindow.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.
    

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

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

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

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

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

Асинхронный метод, возвращающий задачу, возвращает экземпляр задачи, когда элемент управления возвращается к вызывающему объекту. Управление передается от асинхронного метода его вызывающему объекту, когда в вызванном методе обнаруживается оператор Await или await, или вызванный метод завершается. Отображаемые строки, которые помечены от "ТРИ" до "ШЕСТЬ", отслеживают эту часть процесса.

Шаг ТРИ

В 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("https://msdn.microsoft.com")

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

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

Шаг ЧЕТЫРЕ

Объявленный тип возвращаемого значения 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.

Шаг ЧЕТЫРЕ

Шаг ПЯТЬ

Когда 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, который ожидает завершения.

Шаг ШЕСТЬ

Когда 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)