Control Flow in Async Programs (Visual Basic) (Поток управления в асинхронных программах (Visual Basic))

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

Примечание.

Ключевые слова Async и Await появились в Visual Studio 2012.

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

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

  • startButton_Click, который вызывает метод AccessTheWebAsync и выводит результат;

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

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

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

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 &=
            vbCrLf & $"Length of the downloaded string: {contentLength}." & vbCrLf

    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://learn.microsoft.com")

        ' THREE
        Dim urlContents As String = Await getStringTask

        ' FIVE
        Return urlContents.Length
    End Function

End Class

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

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 или более поздней версии и .NET Framework 4.5 или более поздней версии.

Скачайте программу

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

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

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

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

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

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

Чтобы запустить проект, выполните следующие действия.

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

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

    Откроется диалоговое окно Создание проекта .

  3. В области установленных шаблонов выберите Visual Basic и выберите приложение WPF из списка типов проектов.

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

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

  5. В редакторе кода Visual Studio перейдите на вкладку MainWindow.xaml .

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

  6. Замените код в представлении XAML файла MainWindow.xaml на следующий.

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

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

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

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

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

    ' 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://learn.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
    
  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 вызывает асинхронный метод HttpClientGetStringAsync(String). Ниже показаны вызовы из метода в метод.

Steps ONE and TWO

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

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

Шаг ТРИ

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

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

Dim getStringTask As Task(Of String) = client.GetStringAsync("https://learn.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

На следующем рисунке показан поток управления из client.GetStringAsync назначения в создание getStringTask и от создания getStringTask приложения оператора Await.

Step THREE

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

Примечание.

Как правило, ожидание вызова асинхронного метода выполняется немедленно. Например, следующее присвоение может заменить предыдущий код, который создает, а затем ожидает getStringTask: Dim urlContents As String = Await client.GetStringAsync("https://learn.microsoft.com")

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

Шаг ЧЕТЫРЕ

Объявленный тип возвращаемого значения AccessTheWebAsync — Task(Of Integer). Таким образом, когда выполнение AccessTheWebAsync приостанавливается, он возвращает задачу целого числа в startButton_Click. Следует понимать, что возвращаемая задача — не getStringTask. Возвращаемая задача — это новая задача целого числа, представляющая оставшуюся работу в приостановленном методе AccessTheWebAsync. Задача является обещанием AccessTheWebAsync создать фактическое целое число после завершения задачи.

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

Dim getLengthTask As Task(Of Integer) = 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

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

Step FOUR

Шаг ПЯТЬ

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

Step FIVE

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

На следующем рисунке показано возвращение управления от AccessTheWebAsync к startButton_Click.

Step SIX

См. также