异步程序中的控制流 (Visual Basic)

使用AsyncAwait关键字可以更轻松地编写和维护异步程序。 但是,如果不了解程序的运作方式,结果可能会令你大吃一惊。 本主题通过一个简单的异步程序跟踪控制流的流动,展示当控制权从一个方法移动到另一个方法时,每次传递了哪些信息。

注释

Visual Studio 2012 中引入了关键字 AsyncAwait 关键字。

通常,使用 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 或更高版本。

下载程序

可以从 异步示例:异步程序中的控制流下载本主题的应用程序。 以下步骤打开并运行程序。

  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 的快捷菜单,然后选择“ 查看代码”。

  6. 在 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 的设计 视图中。

  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 键,然后选择 “开始 ”按钮。

    应显示以下输出:

    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 调用 AccessTheWebAsyncAccessTheWebAsync 调用异步 HttpClient 方法 GetStringAsync(String) 时,前两个显示行会跟踪路径。 下图概述了方法之间的调用。

步骤一和两个

AccessTheWebAsyncclient.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 运算符的应用程序。

步骤 3

Await 表达式将暂停 AccessTheWebAsync,直到返回 client.GetStringAsync。 在此期间,控制权将返回给AccessTheWebAsyncstartButton_Click的调用者。

注释

通常,请立即等待对异步方法的调用。 例如,以下赋值可以替换前面创建的代码,然后等待 getStringTaskDim 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 处于等待状态。

步骤 4

步骤 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 表达式从 getLengthTaskstartButton_Click检索该值。

下图显示了完成 client.GetStringAsync(和 getStringTask)后控制的转移。

步骤 5

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

步骤 6

另请参阅