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

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

Замечание

Ключевые слова Async и Await были введены в Visual Studio 2012.

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

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

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

  • AccessTheWebAsync, который скачивает содержимое веб-сайта в виде строки и возвращает длину строки. AccessTheWebAsync использует асинхронный HttpClient метод, GetStringAsync(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" до "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 или более поздней версии и .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 Code выберите вкладку MainWindow.xaml .

    Если вкладка не отображается, откройте контекстное меню mainWindow.xaml в обозревателе решений и выберите команду View Code.

  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). На следующем рисунке показаны переходы от одного метода к другому.

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

Возвращаемый тип 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 возвращает задачу типа string, которая назначена переменной getStringTask в AccessTheWebAsync. В следующей строке в примере программы показан вызов client.GetStringAsync и назначение.

Dim getStringTask As Task(Of String) = client.GetStringAsync("https://learn.microsoft.com")

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

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.

Шаг ТРИ

Выражение 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 не будет ожидаемым.

Шаг Четыре

Шаг ПЯТЬ

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

На изображении ниже показана передача контроля от AccessTheWebAsync к startButton_Click.

Шаг ШЕСТЬ

См. также