使用Async和Await进行异步编程(VB.Net版)
Visual Basic 中的 Async 和 Await 关键字是异步编程的核心。 通过这两个关键字,可以使用 .NET Framework 或 Windows 运行时中的资源轻松创建异步方法(几乎与创建同步方法一样轻松)。 使用 Async
和 Await
定义的异步方法简称为异步 (Async) 方法。
下面的示例演示了一种异步方法。 你应对代码中的几乎所有内容都非常熟悉。 注释调出你添加的用来创建异步的功能。
此主题的末尾提供完整的 Windows Presentation Foundation (WPF) 示例文件,请从异步示例:“使用 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”后缀结尾。
- 返回类型为下列类型之一:
- 任务(TResult)如果方法具有返回语句,其中操作数具有 TResult 类型。
- 如果你的方法没有返回语句或具有没有操作数的返回语句,则为 Task。
- Sub:如果要编写异步事件处理程序。
有关详细信息,请参见本主题后面的“返回类型和参数”。
- 方法通常包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。 同时,将方法挂起,并且控件返回到方法的调用方。 本主题的下一节将解释悬挂点发生的情况。
在异步方法中,可使用提供的关键字和类型来指示需要完成的操作,且编译器会完成其余操作,其中包括持续跟踪控件以挂起方法返回等待点时发生的情况。 一些常规流程(例如,循环和异常处理)在传统异步代码中处理起来可能很困难。 在异步方法中,元素的编写频率与同步解决方案相同且此问题得到解决。
若要详细了解旧版 .NET Framework 中的异步性,请参阅 TPL 和传统 .NET Framework 异步编程。
在异步方法中发生的情况
异步编程中最需弄清的是控制流是如何从方法移动到方法的。 下图可引导你完成此过程:
图中的数字对应于以下步骤:
- 事件处理程序调用并等待
AccessTheWebAsync
异步方法。 AccessTheWebAsync
可创建 HttpClient 实例并调用 GetStringAsync 异步方法以下载网站内容作为字符串。GetStringAsync
中发生了某种情况,该情况挂起了它的进程。 可能必须等待网站下载或一些其他阻止活动。 为避免阻止资源,GetStringAsync
会将控制权出让给其调用方AccessTheWebAsync
。GetStringAsync
返回 TResult 是字符串的任务(TResult),并将AccessTheWebAsync
任务分配给getStringTask
变量。 该任务表示调用GetStringAsync
的正在进行的进程,其中承诺当工作完成时产生实际字符串值。- 由于尚未等待
getStringTask
,因此,AccessTheWebAsync
可以继续执行不依赖于GetStringAsync
得出的最终结果的其他工作。该任务由对同步方法DoIndependentWork
的调用表示。 DoIndependentWork
是完成其工作并返回其调用方的同步方法。AccessTheWebAsync
已运行完毕,可以不受getStringTask
的结果影响。 接下来,AccessTheWebAsync
需要计算并返回已下载的字符串的长度,但该方法只有在获得字符串的情况下才能计算该值。因此,
AccessTheWebAsync
使用一个 await 运算符来挂起其进度,并把控制权交给调用AccessTheWebAsync
的方法。AccessTheWebAsync
将Task(Of Integer)
返回给调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺。备注
如果
GetStringAsync
(因此getStringTask
)在AccessTheWebAsync
等待前完成,则控制会保留在AccessTheWebAsync
中。如果异步调用过程 (AccessTheWebAsync
) 已完成,并且 AccessTheWebSync 不必等待最终结果,则挂起然后返回到getStringTask
将造成成本浪费。在调用方内部(此示例中的事件处理程序),处理模式将继续。 在等待结果前,调用方可以开展不依赖于
AccessTheWebAsync
结果的其他工作,否则就需等待片刻。 事件处理程序等待AccessTheWebAsync
,而AccessTheWebAsync
等待GetStringAsync
。GetStringAsync
完成并生成一个字符串结果。 字符串结果不是通过按你预期的方式调用GetStringAsync
所返回的。 (请记住,此方法已在步骤 3 中返回一个任务。)相反,字符串结果存储在表示完成方法getStringTask
的任务中。 await 运算符从getStringTask
中检索结果。 赋值语句将检索到的结果赋给urlContents
。- 当
AccessTheWebAsync
具有字符串结果时,该方法可以计算字符串长度。 然后,AccessTheWebAsync
工作也将完成,并且等待事件处理程序可继续使用。 在此主题结尾处的完整示例中,可确认事件处理程序检索并打印长度结果的值。
如果你不熟悉异步编程,请花 1 分钟时间考虑同步行为和异步行为之间的差异。 当其工作完成时(第 5 步)会返回一个同步方法,但当其工作挂起时(第 3 步和第 6 步),异步方法会返回一个任务值。 在异步方法最终完成其工作时,任务会标记为已完成,而结果(如果有)将存储在任务中。
若要详细了解控制流,请参阅异步程序中的控制流 (Visual Basic)。
API 异步方法
你可能想知道从何处可以找到 GetStringAsync
等支持异步编程的方法。 .NET 框架 4.5 或更高版本包含许多使用Async
和Await
的成员。 您可以通过附加到成员名称的”Async”后缀和 或Task任务(TResult)的返回类型来识别这些成员。 例如,System.IO.Stream
类包含 CopyToAsync、ReadAsync 和 WriteAsync 等方法,以及同步方法 CopyTo、Read 和 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:
' 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
。
相关主题和示例
完整示例
下面的代码来自于本主题介绍的 Windows Presentation Foundation (WPF) 应用程序的 MainWindow.xaml.vb 文件。 可以从异步示例:“使用 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