Visual Basic 中的 Async  Await 关键字是异步编程的核心。 通过这两个关键字,可以使用 .NET Framework 或 Windows 运行时中的资源轻松创建异步方法(几乎与创建同步方法一样轻松)。 使用 Async  Await 定义的异步方法简称为异步 (Async) 方法。

下面的示例演示了一种异步方法。 你应对代码中的几乎所有内容都非常熟悉。 注释调出你添加的用来创建异步的功能。

此主题的末尾提供完整的 Windows Presentation Foundation (WPF) 示例文件,请从异步示例:“使用 Async 和 Await 的异步编程”示例下载此示例。

VB
' 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 和等待其完成期间不能进行任何工作,则你可以通过在下面的单个语句中调用和等待来简化代码。

VB
Dim urlContents As String = Await client.GetStringAsync()

以下特征总结了使上一个示例成为异步方法的原因:

  • 方法签名包含 Async 修饰符。
  • 按照约定,异步方法的名称以“Async”后缀结尾。
  • 返回类型为下列类型之一:
    • 任务(TResult)如果方法具有返回语句,其中操作数具有 TResult 类型。
    • 如果你的方法没有返回语句或具有没有操作数的返回语句,则为 Task
    • Sub:如果要编写异步事件处理程序。

    有关详细信息,请参见本主题后面的“返回类型和参数”。

  • 方法通常包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。 同时,将方法挂起,并且控件返回到方法的调用方。 本主题的下一节将解释悬挂点发生的情况。

在异步方法中,可使用提供的关键字和类型来指示需要完成的操作,且编译器会完成其余操作,其中包括持续跟踪控件以挂起方法返回等待点时发生的情况。 一些常规流程(例如,循环和异常处理)在传统异步代码中处理起来可能很困难。 在异步方法中,元素的编写频率与同步解决方案相同且此问题得到解决。

若要详细了解旧版 .NET Framework 中的异步性,请参阅 TPL 和传统 .NET Framework 异步编程

在异步方法中发生的情况

异步编程中最需弄清的是控制流是如何从方法移动到方法的。 下图可引导你完成此过程:

展示了如何跟踪异步程序的图。

图中的数字对应于以下步骤:

  1. 事件处理程序调用并等待 AccessTheWebAsync 异步方法。
  2. AccessTheWebAsync 可创建 HttpClient 实例并调用 GetStringAsync 异步方法以下载网站内容作为字符串。
  3. GetStringAsync 中发生了某种情况,该情况挂起了它的进程。 可能必须等待网站下载或一些其他阻止活动。 为避免阻止资源,GetStringAsync 会将控制权出让给其调用方 AccessTheWebAsync

    GetStringAsync返回 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) 已完成,并且 AccessTheWebSync 不必等待最终结果,则挂起然后返回到 getStringTask 将造成成本浪费。

    在调用方内部(此示例中的事件处理程序),处理模式将继续。 在等待结果前,调用方可以开展不依赖于 AccessTheWebAsync 结果的其他工作,否则就需等待片刻。 事件处理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync

  7. GetStringAsync 完成并生成一个字符串结果。 字符串结果不是通过按你预期的方式调用 GetStringAsync 所返回的。 (请记住,此方法已在步骤 3 中返回一个任务。)相反,字符串结果存储在表示完成方法 getStringTask 的任务中。 await 运算符从 getStringTask 中检索结果。 赋值语句将检索到的结果赋给 urlContents
  8.  AccessTheWebAsync 具有字符串结果时,该方法可以计算字符串长度。 然后,AccessTheWebAsync 工作也将完成,并且等待事件处理程序可继续使用。 在此主题结尾处的完整示例中,可确认事件处理程序检索并打印长度结果的值。

如果你不熟悉异步编程,请花 1 分钟时间考虑同步行为和异步行为之间的差异。 当其工作完成时(第 5 步)会返回一个同步方法,但当其工作挂起时(第 3 步和第 6 步),异步方法会返回一个任务值。 在异步方法最终完成其工作时,任务会标记为已完成,而结果(如果有)将存储在任务中。

若要详细了解控制流,请参阅异步程序中的控制流 (Visual Basic)

API 异步方法

你可能想知道从何处可以找到 GetStringAsync 等支持异步编程的方法。 .NET 框架 4.5 或更高版本包含许多使用AsyncAwait的成员。 您可以通过附加到成员名称的”Async”后缀和 或Task任务(TResult)的返回类型来识别这些成员。 例如,System.IO.Stream 类包含 CopyToAsyncReadAsync  WriteAsync 等方法,以及同步方法 CopyToRead  Write

Windows 运行时也包含许多可以在 Windows 应用中与 Async  Await 结合使用的方法。 有关详细信息和示例方法,请参阅在C# 或可视基本异步编程(Windows 运行时应用)WhenAny:.NET 框架和 Windows 运行时之间桥接调用异步 API。

线程

异步方法旨在成为非阻止操作。 异步方法中的 Await 表达式在等待的任务正在运行时不会阻止当前线程。 相反,表达式在继续时注册方法的其余部分并将控件返回到异步方法的调用方。

Async  Await 关键字不会创建其他线程。 异步方法不需要多线程,因为异步方法不在其自己的线程上运行。 只有当方法处于活动状态时,该方法将在当前同步上下文中运行并使用线程上的时间。 可以使用 Task.Run 将占用大量 CPU 的工作移到后台线程,但是后台线程不会帮助正在等待结果的进程变为可用状态。

对于异步编程而言,该基于异步的方法优于几乎每个用例中的现有方法。 特别是,此方法优于BackgroundWorkerI/O 绑定操作,因为代码更简单,您不必防范争用条件。 结合 Task.Run 使用时,异步编程比 BackgroundWorker 更适用于 CPU 绑定的操作,因为异步编程将运行代码的协调细节与 Task.Run 传输至线程池的工作区分开来。

异步和等待

如果使用 Async 修饰符将某种方法指定为异步方法,即启用以下两种功能。

  • 标记的异步方法可以使用 Await 来指定暂停点。 await 运算符通知编译器异步方法只有直到等待的异步过程完成才能继续通过该点。 同时,控制返回至异步方法的调用方。

    异步方法在 Await 表达式执行时暂停并不构成方法退出,只会导致 Finally 代码块不运行。

  • 标记的异步方法本身可以通过调用它的方法等待。

异步方法通常包含 Await 运算符的一个或多个实例,但缺少 Await 表达式也不会导致生成编译器错误。 如果异步方法未使用 Await运算符标记暂停点,则该方法会作为同步方法执行,即使有 Async 修饰符,也不例外。 编译器将为此类方法发布一个警告。

Async  Await 都是上下文关键字。 有关详细信息和示例,请参阅下列主题:

返回类型和参数

在 .NET 框架编程中,异步方法通常返回Task任务(TResult)。 在异步方法中,Await 运算符应用于通过调用另一个异步方法返回的任务。

如果方法包含指定类型TResult操作数的Return语句,则指定Task(TResult)作为返回类型。

如果方法不含任何 return 语句或包含不返回操作数的 return 语句,则将 Task 用作返回类型。

下面的示例演示如何声明和调用返回Task(TResult)或 的方法Task

VB
' 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 参数,但可以调用包含此类参数的方法。

有关详细信息和示例,请参阅异步返回类型 (Visual Basic) 若要详细了解如何在异步方法中捕获异常,请参阅 Try…Catch…Finally 语句

Windows 运行时编程中的异步 API 具有下列返回类型之一(类似于任务):

有关详细信息和示例,请参阅在 C# 或可视化基本 中调用异步 API。

命名约定

按照约定,将“Async”追加到包含 Async 修饰符的方法的名称中。

如果某一约定中的事件、基类或接口协定建议其他名称,则可以忽略此约定。 例如,你不应重命名常用事件处理程序,例如 Button1_Click

表 2
标题 说明 示例
演练:使用 Async 和 Await 访问 Web (Visual Basic) 演示如何将一个同步 WPF 解决方案转换成一个异步 WPF 解决方案。 应用程序下载一系列网站。 异步示例:“访问 Web”演练
如何:使用 Task.WhenAll 扩展异步演练 (Visual Basic)  Task.WhenAll 添加到上一个演练。 使用 WhenAll 同时启动所有下载。
如何:使用 Async 和 Await 并行发出多个 Web 请求 (Visual Basic) 演示如何同时开始几个任务。 异步示例:并行发出多个 Web 请求
异步返回类型 (Visual Basic) 描述异步方法可返回的类型,并解释每种类型适用于的情况。
异步程序中的控制流 (Visual Basic) 通过异步程序中的一系列 await 表达式来详细跟踪控制流。 异步示例:异步程序中的控制流
微调异步应用程序 (Visual Basic) 演示如何将以下功能添加到异步解决方案:

 取消一个异步任务或一组任务 (Visual Basic)
 在一段时间后取消异步任务 (Visual Basic)
 在完成一个异步任务后取消剩余任务 (Visual Basic)
 启动多个异步任务并在其完成时进行处理 (Visual Basic)

Async Sample: Fine Tuning Your Application(异步示例:微调应用程序)
在异步应用程序中处理重入 (Visual Basic) 演示如何处理有效的异步操作在运行时重启的情况。
WhenAny:.NET Framework 和 Windows 运行时之间的桥接 展示了如何在 .NET Framework 与 Windows 运行时中的 IAsyncOperations 之间桥接任务类型,以便可以将 WhenAny 与 Windows 运行时方法结合使用。 异步示例:桥接 .NET 和 Windows 运行时(AsTask 和 WhenAny)
异步取消:.NET Framework 和 Windows 运行时之间的桥接 展示了如何在 .NET Framework 与 Windows 运行时中的 IAsyncOperations 之间桥接任务类型,以便可以将 CancellationTokenSource 与 Windows 运行时方法结合使用。 异步示例:桥接 .NET 和 Windows 运行时(AsTask 和 Cancellation)
使用 Async 进行文件访问 (Visual Basic) 列出并演示使用 async 和 await 访问文件的好处。
基于任务的异步模式 (TAP) 描述 .NET Framework 中异步的新模式。 该模式基于 和Task任务(TResult)类型。
Channel 9 上的异步视频 提供指向有关异步编程的各种视频的链接。

完整示例

下面的代码来自于本主题介绍的 Windows Presentation Foundation (WPF) 应用程序的 MainWindow.xaml.vb 文件。 可以从异步示例:“使用 Async 和 Await 的异步编程”示例下载示例。

VB

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