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

可以使用 AsyncAwait 关键字更加轻松地编写和维护异步程序。 但是,如果不了解程序的运行方式,结果可能会让你大吃一惊。 此主题通过一个简单的异步程序跟踪控制流,以显示控制从一种方法移动到另一种方法的情况,以及每次所传输的信息。

注意

AsyncAwait 关键字是在 Visual Studio 2012 中引入的。

一般情况下,使用 Async 修饰符标记包含异步代码的方法。 在使用 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

每个标记位置(“1”到“6”)显示有关该程序的当前状态的信息。 将生成以下输出:

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 代码编辑器中,选择 “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) 时,前两个显示行会跟踪路径。 下图概述了从方法到方法的调用。

Steps ONE and TWO

AccessTheWebAsyncclient.GetStringAsync 的返回类型都是 Task<TResult>。 对于 AccessTheWebAsync,TResult 是一个整数。 对于 GetStringAsync,TResult 是一个字符串。 有关异步方法返回类型的详细信息,请参阅异步返回类型 (Visual Basic)

在控制切换回调用方时,任务返回异步方法将返回一个任务实例。 在调用的方法中遇到 Await 运算符或在调用的方法结束时,控制会从异步方法返回其调用方。 标记为“3”到“6”的显示行将跟踪过程的这一部分。

步骤 3

AccessTheWebAsync 中,调用异步方法 GetStringAsync(String) 以下载目标网页的内容。 返回 client.GetStringAsync 时,控制将从 client.GetStringAsync 返回到 AccessTheWebAsync

client.GetStringAsync 方法将返回分配给 AccessTheWebAsync 中的 getStringTask 变量的字符串任务。 该示例程序中的以下行说明了对 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.

getStringTask 处于等待状态时,下面的语句将暂停 AccessTheWebAsync 中的进度。

Dim urlContents As String = Await getStringTask

下图显示了从 client.GetStringAsync 的控制流到 getStringTask 的赋值和从 getStringTask 的创建到 await 运算符的应用程序。

Step THREE

Await 表达式将暂停 AccessTheWebAsync,直到返回 client.GetStringAsync。 同时,控件返回至 AccessTheWebAsync 的调用方 startButton_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.

getLengthTask 处于等待状态时,startButton_Click 中的进度将挂起。 下面的赋值语句将挂起 startButton_Click,直到完成 AccessTheWebAsync

Dim contentLength As Integer = Await getLengthTask

下图中的箭头显示控制流,该控制流从 AccessTheWebAsync 中的 await 表达式到 getLengthTask 的赋值值,后跟 startButton_Click 中的正常处理,直到 getLengthTask 处于等待状态。

Step FOUR

步骤 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 表达式从 startButton_Click 中的 getLengthTask 检索该值。

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

Step FIVE

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 中 return 语句的操作数的整数值。 下面的语句将该值赋给 contentLength 变量。

Dim contentLength As Integer = Await getLengthTask

下图显示从 AccessTheWebAsyncstartButton_Click 的控制的返回。

Step SIX

另请参阅