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

使用 Async 和 Await 关键字,可以编写和更易于维护异步程序。 但是,结果可能让您很诧异,如果不知道程序如何运行。 本主题跟踪控制流通过简单的异步过程显示了,当控件从一个方法移动到另一页时,并在调用信息。

备注

Async 和 Await 关键字在 Visual Studio 2012 中引入)。有关在该版本中的新功能的更多信息,请参见 Visual Studio 2012 中的新增功能

通常,您标记包含与 Async (Visual Basic)" (c#) 修饰符的异步代码的方法。 在标有"修饰符的方法,可以使用 等待 (Visual Basic)等待 (c#) 运算符指定等待的方法暂停调用了异步过程完成。 有关更多信息,请参见使用 Async 和 Await 的异步编程(C# 和 Visual Basic)

下面的示例使用异步方法下载一个指定网站的内容作为字符串和显示该字符串的长度。 该示例包含以下两种方法。

  • startButton_Click,调用 AccessTheWebAsync 并显示结果。

  • AccessTheWebAsync,下载网站内容作为字符串并返回该字符串的长度。 AccessTheWebAsync 使用异步 HttpClient 方法,GetStringAsync(String),下载内容。

计算的显示行显示的战略点在程序中帮助您理解程序运行方式,并说明发生在每个已标记。 显示行标记为“一”通过“、”。标签表示程序到达这些代码行的顺序。

下面的代码显示程序的轮廓。

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 &=
            String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength)

    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://msdn.microsoft.com")

        ' THREE
        Dim urlContents As String = Await getStringTask

        ' FIVE
        Return urlContents.Length
    End Function

End Class
public partial class MainWindow : Window
{
    // . . .
    private async void startButton_Click(object sender, RoutedEventArgs e)
    {
        // ONE
        Task<int> getLengthTask = AccessTheWebAsync();

        // FOUR
        int contentLength = await getLengthTask;

        // SIX
        resultsTextBox.Text +=
            String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
    }


    async Task<int> AccessTheWebAsync()
    {
        // TWO
        HttpClient client = new HttpClient();
        Task<string> getStringTask =
            client.GetStringAsync("https://msdn.microsoft.com");

        // THREE                 
        string urlContents = await getStringTask;

        // FIVE
        return urlContents.Length;
    }
}

每个标记的位置,“的”通过“六,”显示有关程序的当前状态的信息。 将产生以下输出。

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 中,Visual Studio express 2012 中或在您的计算机上安装 .NET framework 4.5。

Hh873191.collapse_all(zh-cn,VS.110).gif下载程序

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

  1. 提取下载的文件将压缩,然后启动 Visual Studio 2012。

  2. 在菜单栏上,依次选择**“文件”“打开”“项目/解决方案”**。

  3. 定位到包含提取的代码示例的文件夹中,打开解决方案文件 (.sln),然后选择 F5 键生成和运行项目。

Hh873191.collapse_all(zh-cn,VS.110).gif生成程序

以下 windows 演示基础 (WPF) 项包含此主题的代码示例。

若要运行项目,请执行以下步骤:

  1. 启动 Visual Studio。

  2. 在菜单栏上,选择**“文件”“新建**、“项目”

    将打开**“新建项目”**对话框。

  3. 已安装的模板 窗格中,选择 Visual BasicVisual C#,从项目类型列表然后选择 WPF 应用程序

  4. 输入 AsyncTracer 作为项目的名称,然后选择 确定 按钮。

    新项目出现在**“解决方案资源管理器”**中。

  5. 在 Visual Studio 代码编辑器"中,选择 MainWindow.xaml 选项。

    如果看不到选项卡,打开 MainWindow.xaml 的快捷菜单在 解决方案资源管理器,然后选择 查看代码

  6. 在 MainWindow.xaml XAML 视图中,用以下代码替换代码。

    <Window
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://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>
    
    <Window
            xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="AsyncTracer.MainWindow"
            Title="Control Flow Trace" Height="350" Width="592">
        <Grid>
            <Button x:Name="startButton" Content="Start&#xa;" HorizontalAlignment="Left" Margin="250,10,0,0" VerticalAlignment="Top" Width="75" Height="24"  Click="startButton_Click" d:LayoutOverrides="GridBox"/>
            <TextBox x:Name="resultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="576" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" Grid.ColumnSpan="3"/>
        </Grid>
    </Window>
    

    包含文本框和一个按钮的简单窗口显示 MainWindow.xaml 设计 视图。

  7. 添加 System.Net.Http的引用。

  8. 解决方案资源管理器,请打开 MainWindow.xaml.vb 或 MainWindow.xaml.cs 的快捷菜单,然后选择 查看代码

  9. 在 MainWindow.xaml.vb 或 MainWindow.xaml.cs 中,用以下代码替换代码。

    ' 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://msdn.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
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    // Add a using directive and a reference for System.Net.Http;
    using System.Net.Http;
    
    namespace AsyncTracer
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private async void startButton_Click(object sender, RoutedEventArgs e)
            {
                // The display lines in the example lead you through the control shifts.
                resultsTextBox.Text += "ONE:   Entering startButton_Click.\r\n" +
                    "           Calling AccessTheWebAsync.\r\n";
    
                Task<int> getLengthTask = AccessTheWebAsync();
    
                resultsTextBox.Text += "\r\nFOUR:  Back in startButton_Click.\r\n" +
                    "           Task getLengthTask is started.\r\n" +
                    "           About to await getLengthTask -- no caller to return to.\r\n";
    
                int contentLength = await getLengthTask;
    
                resultsTextBox.Text += "\r\nSIX:   Back in startButton_Click.\r\n" +
                    "           Task getLengthTask is finished.\r\n" +
                    "           Result from AccessTheWebAsync is stored in contentLength.\r\n" +
                    "           About to display contentLength and exit.\r\n";
    
                resultsTextBox.Text +=
                    String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
            }
    
    
            async Task<int> AccessTheWebAsync()
            {
                resultsTextBox.Text += "\r\nTWO:   Entering AccessTheWebAsync.";
    
                // Declare an HttpClient object.
                HttpClient client = new HttpClient();
    
                resultsTextBox.Text += "\r\n           Calling HttpClient.GetStringAsync.\r\n";
    
                // GetStringAsync returns a Task<string>. 
                Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");
    
                resultsTextBox.Text += "\r\nTHREE: Back in AccessTheWebAsync.\r\n" +
                    "           Task getStringTask is started.";
    
                // AccessTheWebAsync can continue to work until getStringTask is awaited.
    
                resultsTextBox.Text +=
                    "\r\n           About to await getStringTask and return a Task<int> to startButton_Click.\r\n";
    
                // Retrieve the website contents when task is complete.
                string urlContents = await getStringTask;
    
                resultsTextBox.Text += "\r\nFIVE:  Back in AccessTheWebAsync." +
                    "\r\n           Task getStringTask is complete." +
                    "\r\n           Processing the return statement." +
                    "\r\n           Exiting from AccessTheWebAsync.\r\n";
    
                return urlContents.Length;
            }
        }
    }
    
  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.
    

跟踪程序

Hh873191.collapse_all(zh-cn,VS.110).gif执行一个和两

前两行显示跟踪作为 startButton_Click 的路径调用 AccessTheWebAsync,并且,AccessTheWebAsync 异步调用 HttpClient 方法 GetStringAsync(String)。 下图显示从方法调用方法。

步骤 1 和步骤 2

两 AccessTheWebAsync 和 client.GetStringAsync 的返回类型为 Task<TResult>。 对于 AccessTheWebAsync,TResult 是整数。 对于 GetStringAsync,TResult 是字符串。 有关异步方法的更多信息返回类型,请参见 异步返回类型(C# 和 Visual Basic)

当控件从状态转换回调用方时,一个任务返回的异步方法返回任务实例。 控件从异步方法返回到其调用方其中之一,当 Await 或 await 运算符在调用方法时遇到或,当调用方法的末尾。 标记为“THREE”通过“、”跟踪过程的此部分中的突出显示该行。

Hh873191.collapse_all(zh-cn,VS.110).gif第三步

在 AccessTheWebAsync,该异步方法 GetStringAsync(String) 调用下载目标网页的内容。 当 client.GetStringAsync 返回时,控件从 client.GetStringAsync 回 AccessTheWebAsync。

client.GetStringAsync 方法返回分配给 AccessTheWebAsync的 getStringTask 变量字符串的任务。 在示例程序的以下行演示对 client.GetStringAsync 和该赋值。

Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");

可以将任务作为提交由 client.GetStringAsync 最终生成一个实际字符串。 同时,因此,如果 AccessTheWebAsync 具有不依赖于从 client.GetStringAsync的已提交的字符串执行的工作,该工作才能继续,当 client.GetStringAsync 等待时。 在此示例中,输出下行,标记为“THREE”,表示机会完成独立工作

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
string urlContents = await getStringTask;

下图显示了控制流从 client.GetStringAsync 到该分配给 getStringTask 和从 getStringTask 的创建用于等待运算符的应用程序。

步骤 3

等待表达式挂起 AccessTheWebAsync,直到 client.GetStringAsync 返回。 同时,控件将恢复 AccessTheWebAsync,startButton_Click的调用方。

备注

通常,您会立即等待调用异步方法。例如,以下分配之一可能替换创建然后等待 getStringTask的前面的代码:

  • Visual Basic:Dim urlContents As String = Await client.GetStringAsync("https://msdn.microsoft.com")

  • C#:string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");

本主题后,应用等待运算符以指示控制流通过程序的输出行。

Hh873191.collapse_all(zh-cn,VS.110).gif第四个步骤

声明的返回 AccessTheWebAsync 的类型是 Visual Basic 和 Task<int> 的 Task(Of Integer) 在 c# 中。 因此,那么,当 AccessTheWebAsync 挂起时,它返回整数任务 startButton_Click。 您应了解返回的任务并不 getStringTask。 返回的任务是表示整数的新任务任何挂起的方法仍然要执行,AccessTheWebAsync。 当任务完成,任务是从 AccessTheWebAsync 的一个承诺导致整数。

下面的语句分配此任务添加到 getLengthTask 变量。

Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = 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
int contentLength = await getLengthTask;

在下图中,可以在 AccessTheWebAsync 显示控制流从等待表达式的值的资源分配给 getLengthTask,后跟普通处理在 startButton_Click,直到 getLengthTask 等待。

步骤 4

Hh873191.collapse_all(zh-cn,VS.110).gif步骤五

当 client.GetStringAsync 等待操作完成时,处理在 AccessTheWebAsync 来保存释放,并可以继续用于等待语句。 输出下面一行代表进程的恢复。

FIVE:  Back in AccessTheWebAsync.
           Task getStringTask is complete.
           Processing the return statement.
           Exiting from AccessTheWebAsync.

返回语句,urlContents.Length的操作的,在 AccessTheWebAsync 返回的任务存储。 等待表达式将在 startButton_Click的 getLengthTask 检索该值。

下图显示控件转换。client.GetStringAsync (和 getStringTask之后) 已完成。

步骤 5

AccessTheWebAsync 已完成运行,因此,控件将恢复 startButton_Click,等待完成。

Hh873191.collapse_all(zh-cn,VS.110).gif第六步骤

当 AccessTheWebAsync 等待操作完成时,过程可以继续用于在 startButton_Async的时间语句。 实际上,程序都更没有执行。

输出下面一行代表进程的以 startButton_Async:

SIX:   Back in startButton_Click.
           Task getLengthTask is finished.
           Result from AccessTheWebAsync is stored in contentLength.
           About to display contentLength and exit.

等待表达式从 getLengthTask 检索是返回语句操作是在 AccessTheWebAsync的整数值。 下面的语句将该值更改为 contentLength 变量。

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

下图显示控件从返回 AccessTheWebAsync 到 startButton_Click。

步骤 6

请参见

任务

演练:使用 Async 和 Await 访问 Web(C# 和 Visual Basic)

演练:将调试器与异步方法一起使用

概念

使用 Async 和 Await 的异步编程(C# 和 Visual Basic)

异步返回类型(C# 和 Visual Basic)

其他资源

Async 示例:在异步程序的控制流 (C# 和 Visual Basic)