Асинхронное программирование с помощью Async и Await (Visual Basic)

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

Visual Studio 2012 г. появился упрощенный подход, асинхронное программирование, которое использует асинхронную поддержку в платформа .NET Framework 4.5 и более поздних версий, а также в среда выполнения Windows. Компилятор выполняет сложную работу, которую раньше делал разработчик, при этом логическая структура кода приложения похожа на синхронный код. То есть можно пользоваться всеми преимуществами асинхронного программирования с гораздо меньшими, чем обычно, трудозатратами.

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

Async повышает скорость реагирования

Асинхронность необходимо использовать при наличии потенциально блокирующих действий — например, когда приложение подключается к Интернету. Доступ к веб-ресурсу иногда осуществляется медленно или с задержкой. Если такое действие блокируется в пределах синхронного процесса, все приложение вынуждено ожидать. В асинхронном процессе приложение может перейти к следующей операции, не зависящей от веб-ресурса, до завершения блокирующей задачи.

В следующей таблице показаны стандартные области, в которых асинхронное программирование повышает скорость реагирования. Перечисленные API из платформа .NET Framework 4.5 и среда выполнения Windows содержат методы, поддерживающие асинхронное программирование.

Область приложения Поддерживающие интерфейсы API, которые содержат асинхронные методы
Веб-доступ HttpClient, SyndicationClient
Работа с файлами StorageFile, StreamWriter, StreamReader, XmlReader
Работа с образами MediaCapture, BitmapEncoder, BitmapDecoder
Программирование с использованием WCF Синхронные и асинхронные операции

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

При использовании асинхронных методов приложение продолжает реагировать на действия в пользовательском интерфейсе. Например, можно изменить размер окна или свернуть его, либо закрыть приложение, если не требуется ждать завершения работы.

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

Методы Async проще создавать

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

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

Полный файл примера Windows Presentation Foundation представлен в конце раздела. Также его можно скачать на странице примера асинхронной работы из руководства по использованию ключевых слов Async и Await.

' Three things to note about writing an Async Function:
'  - The function has an Async modifier.
'  - Its return type is Task or Task(Of T). (See "Return Types" section.)
'  - As a matter of convention, its name ends in "Async".
Async Function AccessTheWebAsync() As Task(Of Integer)
    Using client As New HttpClient()
        ' Call and await separately.
        '  - AccessTheWebAsync can do other things while GetStringAsync is also running.
        '  - getStringTask stores the task we get from the call to GetStringAsync.
        '  - Task(Of String) means it is a task which returns a String when it is done.
        Dim getStringTask As Task(Of String) =
            client.GetStringAsync("https://docs.microsoft.com/dotnet")
        ' You can do other work here that doesn't rely on the string from GetStringAsync.
        DoIndependentWork()
        ' The Await operator suspends AccessTheWebAsync.
        '  - AccessTheWebAsync does not continue until getStringTask is complete.
        '  - Meanwhile, control returns to the caller of AccessTheWebAsync.
        '  - Control resumes here when getStringTask is complete.
        '  - The Await operator then retrieves the String result from getStringTask.
        Dim urlContents As String = Await getStringTask
        ' The Return statement specifies an Integer result.
        ' A method which awaits AccessTheWebAsync receives the Length value.
        Return urlContents.Length

    End Using

End Function

Если метод AccessTheWebAsync не выполняет никакие операции между вызовом метода GetStringAsync и его завершением, можно упростить код, описав вызов и ожидание с помощью следующего простого оператора.

Dim urlContents As String = Await client.GetStringAsync()

Далее поясняется, почему код предыдущего примера является асинхронным методом:

  • Сигнатура метода включает модификатор Async.

  • Имя асинхронного метода, как правило, оканчивается суффиксом Async.

  • Возвращаемое значение имеет один из следующих типов:

    • Task(Of TResult), если у метода есть оператор return, в котором операнд имеет тип TResult.
    • Task, если метод не имеет оператора Return или имеет оператор Return без операнда.
    • Sub, если вы создаете асинхронный обработчик событий.

    Дополнительные сведения см. ниже в подразделе "типы возвращаемого значения и параметры".

  • Метод обычно содержит по крайней мере одно выражение await, отмечающее точку, в которой метод не может выполняться до завершения асинхронной операции. На это время метод приостанавливается и управление возвращается к вызывающему объекту метода. В следующем подразделе показано, что происходит в точке приостановки.

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

Дополнительные сведения об асинхронности в предыдущих версиях платформы .NET Framework, см. в статье TPL and Traditional .NET Framework Asynchronous Programming (Библиотека параллельных задач и традиционное асинхронное программирование в .NET Framework).

Что происходит в асинхронном методе

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

Diagram that shows tracing an async program.

Числа на схеме соответствуют следующим шагам:

  1. Обработчик событий вызывает и ожидает асинхронный метод AccessTheWebAsync.

  2. AccessTheWebAsync создает экземпляр HttpClient и вызывает асинхронный метод GetStringAsync, чтобы загрузить содержимое веб-сайта в виде строки.

  3. В GetStringAsync происходит событие, которое приостанавливает ход выполнения. Например, методу необходимо подождать завершения загрузки или произошло другое блокирующее действие. Чтобы избежать блокировки ресурсов, GetStringAsync передает управление вызывающему объекту AccessTheWebAsync.

    GetStringAsync возвращает task(Of TResult), где TResult является строкой и AccessTheWebAsync назначает задачу переменной getStringTask . Задача представляет собой непрерывный процесс для вызова GetStringAsync с обязательством создать фактическое значение строки, когда работа будет завершена.

  4. Поскольку значение из процесса getStringTask еще не получено, метод AccessTheWebAsync может перейти к другим операциям, не зависящим от конечного результатаGetStringAsync. Эти операции представлены вызовом синхронного метода DoIndependentWork.

  5. DoIndependentWork — это синхронный метод, который выполняет свой код и возвращает управление вызывающему объекту.

  6. Метод AccessTheWebAsync выполнил все операции, для которых не требуется результат процесса getStringTask. Далее метод AccessTheWebAsync должен вычислить длину загруженной строки и возвратить ее, но не может этого сделать, пока нет строки.

    Поэтому AccessTheWebAsync использует оператор await, чтобы приостановить свою работу и передать управление методу, вызвавшему AccessTheWebAsync. AccessTheWebAsync возвращает вызывающему объекту Task(Of Integer). Задача представляет собой обещание создать целочисленный результат, являющийся длиной загруженной строки.

    Примечание

    Если метод GetStringAsync (и, следовательно, процесс getStringTask) выполнен прежде, чем этого дождется AccessTheWebAsync, управление остается у метода AccessTheWebAsync. На приостановку метода AccessTheWebAsync и последующий возврат к нему были бы потрачены лишние ресурсы, если вызываемый асинхронный процесс (getStringTask) уже завершен и AccessTheWebSync не нужно ждать окончательного результата.

    Внутри вызывающего объекта (в данном примере — обработчика событий) шаблон обработки повторяется. Вызывающий объект может выполнять другие операции, не зависящие от результата AccessTheWebAsync, во время ожидания этого результата, или сразу ожидать результата. Обработчик событий ожидает AccessTheWebAsync, а AccessTheWebAsync ожидает GetStringAsync.

  7. GetStringAsync завершается и создает строковый результат. Вызов возвращает строковый результат в метод GetStringAsync, но не так, как, возможно, ожидалось. (Помните, что метод уже возвратил задачу в шаге 3.) Вместо этого строковый результат хранится в задаче getStringTask, которая представляет собой завершение метода. Оператор await извлекает результат из getStringTask. Оператор присваивания назначает извлеченный результат urlContents.

  8. Если AccessTheWebAsync содержит строковый результат, метод может вычислить длину строки. Затем работа AccessTheWebAsync также завершена, и ожидающий обработчик событий может возобновить работу. В полном примере в конце этого раздела видно, что обработчик событий извлекает значение длины и выводит результат.

Если вы недавно занимаетесь асинхронным программированием, рекомендуем обратить внимание на различия между синхронным и асинхронным поведением. Синхронный метод возвращает управление, когда его работа завершается (шаг 5), тогда как асинхронный метод возвращает значение задачи, когда его работа приостанавливается (шаги 3 и 6). Когда асинхронный метод в конечном счете завершает работу, задача помечается как завершенная и результат, при его наличии, сохраняется в задаче.

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

Методы Async API

Где же найти методы для асинхронного программирования (такие как GetStringAsync)? Платформа .NET Framework версии 4.5 или более поздней версии содержит множество элементов, с которыми работает Async и Await. Эти члены можно распознать по суффиксу Async, присоединенному к имени элемента, и типу возвращаемого Task значения или Task(Of TResult). Например, класс System.IO.Stream имеет такие методы, как CopyToAsync, ReadAsync и WriteAsync, наряду с синхронными методами CopyTo, Read и Write.

Среда выполнения Windows также содержит множество методов, которые можно использовать в сочетании с Async и Await в приложениях Windows. Дополнительные сведения и примеры методов см. в статьях Вызов асинхронных API в C# или Visual Basic, асинхронное программирование (среда выполнения Windows приложения) и WhenAny: мост между платформа .NET Framework и среда выполнения Windows .

Потоки

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

Ключевые слова Async и Await не вызывают создания дополнительных потоков. Асинхронные методы не требуют многопотокового использования, так как асинхронный метод не выполняется в собственном потоке. Метод выполняется в текущем контексте синхронизации и использует время в потоке, только когда метод активен. Метод Task.Run можно применять для перемещения операций, использующих ресурсы ЦП, в фоновый поток, однако фоновый поток не имеет смысла применять для процесса, который просто ждет результата.

Асинхронный подход к асинхронному программированию практически по всем параметрам имеет преимущество перед другими подходами. В частности, этот подход лучше, чем BackgroundWorker для операций ввода-вывода, так как код проще и вам не нужно защищаться от условий гонки. В сочетании с Task.Run асинхронное программирование лучше BackgroundWorker для операций, использующих ресурсы ЦП, поскольку отделяет сведения координации о выполнении кода от действий, которые Task.Run перемещает в пул потоков.

Async и Await

Если указать, что метод является асинхронным методом с помощью модификатора Async , включите следующие две возможности.

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

    Приостановка асинхронного метода на выражении Await не считается выходом из метода, и блоки Finally не выполняются.

  • Сам обозначенный асинхронный метод может ожидаться вызывающими его методами.

Асинхронный метод обычно содержит одно или несколько вхождений оператора Await, но отсутствие выражений Await не вызывает ошибок компилятора. Если асинхронный метод не использует оператор Await для обозначения точки приостановки, метод выполняется как синхронный независимо от наличия модификатора Async. При компиляции таких методов выдается предупреждение.

Async и Await являются контекстными ключевыми словами. Дополнительные сведения и примеры см. в следующих разделах:

Типы и параметры возвращаемого значения

В платформа .NET Framework программировании асинхронный метод обычно возвращает Task объект Task(Of TResult). Внутри асинхронного метода оператор Await применяется к задаче, возвращаемой из вызова другого асинхронного метода.

Вы указываете Task(Of TResult) в качестве возвращаемого типа, если метод содержит инструкцию Return , указывающую операнд типа TResult.

В качестве возвращаемого типа используется Task, если метод не содержит операторов return или содержит оператор return, который не возвращает операнд.

В следующем примере показано, как объявлять и вызывать метод, возвращающий метод Task(Of TResult) или :Task

' Signature specifies Task(Of Integer)
Async Function TaskOfTResult_MethodAsync() As Task(Of Integer)

    Dim hours As Integer
    ' . . .
    ' Return statement specifies an integer result.
    Return hours
End Function

' Calls to TaskOfTResult_MethodAsync
Dim returnedTaskTResult As Task(Of Integer) = TaskOfTResult_MethodAsync()
Dim intResult As Integer = Await returnedTaskTResult
' or, in a single statement
Dim intResult As Integer = Await TaskOfTResult_MethodAsync()

' Signature specifies Task
Async Function Task_MethodAsync() As Task

    ' . . .
    ' The method has no return statement.
End Function

' Calls to Task_MethodAsync
Task returnedTask = Task_MethodAsync()
Await returnedTask
' or, in a single statement
Await Task_MethodAsync()

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

Асинхронный метод может также быть методом Sub. Тип возвращаемого значения в основном используется для определения обработчиков событий, где требуется возвращать тип. Асинхронные обработчики событий часто служат в качестве отправной точки для асинхронных программ.

Асинхронный метод, который является процедурой Sub , не может быть ожидается, и вызывающий объект не может перехватывать исключения, вызываемые методом.

Асинхронный метод не может объявлять параметры ByRef, но может вызывать методы, которые имеют такие параметры.

Дополнительные сведения и примеры см. в статье о типах возвращаемых значений асинхронных операций. Дополнительные сведения о перехвате исключений в асинхронных методах см. в статье об операторе Try...Catch...Finally.

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

Дополнительные сведения и пример см. в статье "Вызов асинхронных API" в C# или Visual Basic.

Соглашение об именовании

По соглашению к именам методов, которые имеют модификатор Async, добавляется суффикс Async.

Соглашение можно игнорировать в тех случаях, когда событие, базовый класс или контракт интерфейса предлагает другое имя. Например, не следует переименовывать общие обработчики событий, такие как Button1_Click.

Связанные разделы и примеры (Visual Studio)

Заголовок Описание Пример
Пошаговое руководство. Получение доступа к Интернету с помощью модификатора Async и оператора Await (Visual Basic) Иллюстрирует преобразование синхронного решения WPF в асинхронное. Приложение загружает ряд веб-сайтов. Пример асинхронного программирования с помощью Async и Await (Visual Basic)
How to: Extend the Async Walkthrough by Using Task.WhenAll (Visual Basic) (Практическое руководство. Расширение пошагового руководства по асинхронным процедурам с использованием метода Task.WhenAll (Visual Basic)) Добавляет Task.WhenAll к предыдущему пошаговому руководству. Использование WhenAll запускает все загрузки одновременно.
How to: Make Multiple Web Requests in Parallel by Using Async and Await (Visual Basic) (Практическое руководство. Параллельное выполнение нескольких веб-запросов с использованием Async и Await (Visual Basic)) Иллюстрирует, как запустить несколько задач одновременно. Пример использования Async. Параллельное выполнение нескольких веб-запросов
Async Return Types (Visual Basic) (Типы возвращаемых значений Async (Visual Basic)) Иллюстрирует типы, которые могут возвращать асинхронные методы, и поясняет, когда следует использовать каждый из этих типов.
Control Flow in Async Programs (Visual Basic) (Поток управления в асинхронных программах (Visual Basic)) Выполняет подробную трассировку потока управления через последовательность выражений ожидания в асинхронной программе. Пример использования Async. Поток управления в асинхронных программах
Fine-Tuning Your Async Application (Visual Basic) (Настройка асинхронного приложения (Visual Basic)) Иллюстрирует добавление следующих функциональных возможностей в асинхронное решение:

- Отмена асинхронной задачи или списка задач (Visual Basic)
- Отмена асинхронных задач после периода времени (Visual Basic)
- Cancel Remaining Async Tasks after One Is Complete (Visual Basic) (Отмена оставшихся асинхронных задач после завершения одной из них в Visual Basic)
- Start Multiple Async Tasks and Process Them As They Complete (Visual Basic) (Запуск нескольких асинхронных задач и их обработка по мере завершения в Visual Basic)
Пример использования Async. Настройка приложения
Handling Reentrancy in Async Apps (Visual Basic) (Обработка повторного входа в асинхронных приложениях Visual Basic) Иллюстрирует обработку случаев, в которых активная асинхронная операция перезапускается при выполнении.
WhenAny. Связывание .NET Framework и среды выполнения Windows в C# и Visual Basic Иллюстрирует, как создать мост между типами задач на платформе .NET Framework и IAsyncOperations в среде выполнения Windows, чтобы можно было использовать WhenAny с методом среды выполнения Windows. Пример использования Async. Связывание .NET и среды выполнения Windows с помощью AsTask и WhenAny
Отмена Async. Связывание .NET Framework и среды выполнения Windows Иллюстрирует, как создать мост между типами задач на платформе .NET Framework и IAsyncOperations в среде выполнения Windows, чтобы можно было использовать CancellationTokenSource с методом среды выполнения Windows. Асинхронный пример: объединение между .NET и среда выполнения Windows (отмена AsTask&)
Использование метода Async для доступа к файлам (Visual Basic) Иллюстрирует преимущества использования асинхронности и ожидания для доступа к файлам.
Task-based Asynchronous Pattern (TAP) (Асинхронный шаблон, основанный на задачах (TAP)) Описывает новый шаблон для асинхронности в платформе .NET Framework. Шаблон основан на типах Task и типах Task(Of TResult).
Видеоролики об async на канале Channel 9 Предоставляет ссылки на различные видеоролики об асинхронном программировании.

Полный пример

Ниже представлен код файла MainWindow.xaml.vb из приложения Windows Presentation Foundation (WPF), которое обсуждается в этой статье. Вы можете скачать пример использования Async из руководства по использованию ключевых слов Async и Await.


Imports System.Net.Http

' Example that demonstrates Asynchronous Progamming with Async and Await.
' It uses HttpClient.GetStringAsync to download the contents of a website.
' Sample Output:
' Working . . . . . . .
'
' Length of the downloaded string: 39678.

Class MainWindow

    ' Mark the event handler with Async so you can use Await in it.
    Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)

        ' Call and await immediately.
        ' StartButton_Click suspends until AccessTheWebAsync is done.
        Dim contentLength As Integer = Await AccessTheWebAsync()

        ResultsTextBox.Text &= $"{vbCrLf}Length of the downloaded string: {contentLength}.{vbCrLf}"

    End Sub


    ' Three things to note about writing an Async Function:
    '  - The function has an Async modifier. 
    '  - Its return type is Task or Task(Of T). (See "Return Types" section.)
    '  - As a matter of convention, its name ends in "Async".
    Async Function AccessTheWebAsync() As Task(Of Integer)

        Using client As New HttpClient()

            ' Call and await separately. 
            '  - AccessTheWebAsync can do other things while GetStringAsync is also running.
            '  - getStringTask stores the task we get from the call to GetStringAsync. 
            '  - Task(Of String) means it is a task which returns a String when it is done.
            Dim getStringTask As Task(Of String) =
                client.GetStringAsync("https://docs.microsoft.com/dotnet")

            ' You can do other work here that doesn't rely on the string from GetStringAsync. 
            DoIndependentWork()

            ' The Await operator suspends AccessTheWebAsync.
            '  - AccessTheWebAsync does not continue until getStringTask is complete.
            '  - Meanwhile, control returns to the caller of AccessTheWebAsync.
            '  - Control resumes here when getStringTask is complete.
            '  - The Await operator then retrieves the String result from getStringTask.
            Dim urlContents As String = Await getStringTask

            ' The Return statement specifies an Integer result.
            ' A method which awaits AccessTheWebAsync receives the Length value.
            Return urlContents.Length

        End Using

    End Function

    Sub DoIndependentWork()
        ResultsTextBox.Text &= $"Working . . . . . . .{vbCrLf}"
    End Sub

End Class

См. также