使用Async
和Await
关键字可以更轻松地编写和维护异步程序。 但是,如果不了解程序的运作方式,结果可能会令你大吃一惊。 本主题通过一个简单的异步程序跟踪控制流的流动,展示当控制权从一个方法移动到另一个方法时,每次传递了哪些信息。
注释
Visual Studio 2012 中引入了关键字 Async
和 Await
关键字。
通常,使用 Async 修饰符标记包含异步代码的方法。 在用异步修饰符标记的方法中,可以使用 Await (Visual Basic) 运算符指定方法暂停以等待调用的异步进程完成的位置。 有关详细信息,请参阅使用 Async 和 Await 的异步编程(Visual Basic)。
以下示例使用异步方法将指定网站的内容下载为字符串,并显示字符串的长度。 该示例包含以下两种方法。
startButton_Click
,它调用AccessTheWebAsync
并显示结果。AccessTheWebAsync
,它将网站的内容下载为字符串并返回字符串的长度。AccessTheWebAsync
使用异步 HttpClient 方法 GetStringAsync(String) 来下载内容。
编号显示行显示在整个程序中的战略点,以帮助了解程序运行方式,并解释每个标记点发生的情况。 显示行标记为“ONE”到“SIX”。标签表示程序到达这些代码行的顺序。
以下代码显示了程序的大纲。
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 或更高版本。
下载程序
可以从 异步示例:异步程序中的控制流下载本主题的应用程序。 以下步骤打开并运行程序。
解压缩下载的文件,然后启动 Visual Studio。
在菜单栏上,选择 “文件”、“ 打开”、“ 项目/解决方案”。
导航到保存解压缩的示例代码的文件夹,打开解决方案(.sln)文件,然后选择 F5 键以生成并运行项目。
自行构建程序
以下 Windows Presentation Foundation (WPF) 项目包含本主题的代码示例。
若要运行项目,请执行以下步骤:
启动 Visual Studio。
在菜单栏上,选择 “文件”、“ 新建”、“ 项目”。
此时会打开 “新建项目 ”对话框。
在 “已安装的模板 ”窗格中,选择 Visual Basic,然后从项目类型列表中选择 WPF 应用程序 。
输入
AsyncTracer
为项目的名称,然后选择 “确定 ”按钮。新项目将显示在 解决方案资源管理器中。
在 Visual Studio Code 编辑器中,选择 MainWindow.xaml 选项卡。
如果选项卡不可见,请在 解决方案资源管理器中打开 MainWindow.xaml 的快捷菜单,然后选择“ 查看代码”。
在 MainWindow.xaml 的 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 的设计 视图中。
添加System.Net.Http的引用。
在 解决方案资源管理器中,打开MainWindow.xaml.vb的快捷菜单,然后选择“ 查看代码”。
在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
选择要运行程序的 F5 键,然后选择 “开始 ”按钮。
应显示以下输出:
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.
跟踪程序
步骤 1 和 2
在 startButton_Click
调用 AccessTheWebAsync
及 AccessTheWebAsync
调用异步 HttpClient 方法 GetStringAsync(String) 时,前两个显示行会跟踪路径。 下图概述了方法之间的调用。
AccessTheWebAsync
和 client.GetStringAsync
的返回类型都是 Task<TResult>。 对于 AccessTheWebAsync
,TResult 是一个整数。 对于 GetStringAsync
,TResult 是一个字符串。 有关异步方法返回类型的详细信息,请参阅异步返回类型(Visual Basic)。
当程序控制移回到调用方时,任务返回的异步方法会返回一个任务实例。 在调用的方法中遇到 Await
运算符或在调用的方法结束时,控制会从异步方法返回其调用方。 标记为“3”到“6”的显示行将跟踪过程的这一部分。
步骤 3
在 中 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
等待的同时继续。 在示例中,以下标记为“3”的输出行表示执行独立工作的机会
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 运算符,以容纳通过程序标记控制流的输出行。
步骤 4
声明的 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
下图中的箭头显示控制流,该控制流从 AccessTheWebAsync
中的 await 表达式到 getLengthTask
的赋值值,后跟 startButton_Click
中的正常处理,直到 getLengthTask
处于等待状态。
步骤 5
当 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
,等待完成。
步骤 6
当 AccessTheWebAsync
指示它已完成时,处理可以继续通过 startButton_Async
中的 await 语句。 事实上,该程序没有什么可做的了。
以下输出行表示在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
检索出作为 AccessTheWebAsync
中返回语句的操作数的整数值。 以下语句将该值分配给变量 contentLength
。
Dim contentLength As Integer = Await getLengthTask
下图显示了控件从AccessTheWebAsync
返回到startButton_Click
。