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

可以编写异步程序更易于和直观通过在 Visual Studio 2012中引入的功能。 可以类似于同步代码编写异步代码并让编译器处理异步代码通常需要的难题回调函数和继续。

有关异步功能的更多信息,请参见 使用 Async 和 Await 的异步编程(C# 和 Visual Basic)

本演练从总字节数在网站中列出的一个同步 windows 演示基础 (WPF) 应用程序启动。 使用新功能,本演练然后将应用程序转换为异步解决方案。

如果不希望生成应用程序,您可以下载“Async 示例:访问 Web 演练 (C# 和 Visual Basic)”开发人员代码示例

在本演练中,您将完成以下任务:

  • Create a WPF application.

  • Design a simple WPF MainWindow window.

  • Add a reference.

  • Add Imports statements or using directives.

  • Create a synchronous solution.

  • Test the synchronous solution.

  • Convert GetURLContents to an asynchronous method.

  • Convert SumPageSizes to an asynchronous method.

  • Convert startButton_Click to an asynchronous method.

  • Test the asynchronous solution.

  • Replace GetURLContentsAsync with a .NET Framework method.

  • Complete Code Examples from the Walkthrough

系统必备

必须在计算机上安装Visual Studio 2012。 有关更多信息,请参见 Microsoft 网站

创建 WPF 应用程序

  1. 启动 Visual Studio。

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

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

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

  4. 名称 文本框中,输入 AsyncExampleWPF,然后选择 确定 按钮。

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

设计简单的 WPF MainWindow

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

  2. 如果 工具箱 窗口不可见,请打开 查看 菜单,然后选择 工具箱

  3. 添加一个 按钮 控件和一个 文本框 控件。MainWindow 窗口。

  4. 显示 文本框 控件,因此,在 属性 窗口中,设置下列值:

    • 设置 名称 属性设置为 resultsTextBox。

    • 设置 高度 属性设置为 250。

    • 设置 宽度 属性设置为 500。

    • 文本 选项,请指定一个等宽字体,例如 Lucida 控件台或全局 Monospace。

  5. 显示 按钮 控件,因此,在 属性 窗口中,设置下列值:

    • 设置 名称 属性设置为 开关。

    • 按钮 更改 内容 属性的值更改为 启动

  6. 确定文本框和按钮,以便两个显示 MainWindow 窗口。

    有关 WPF XAML 设计器的更多信息,请参见 使用 XAML 设计器创建 UI

添加引用

  1. 解决方案资源管理器,请将显示项目的名称。

  2. 在菜单栏上,选择 项目添加引用

    引用管理器 出现对话框。

  3. 在对话框顶部,请验证您的项目以 .NET framework 4.5。

  4. 程序集 区域,因此,如果尚未选中复选框,请选中 框架

  5. 在名称列表中,选择 System.Net.Http 复选框。

  6. 选择 确定 按钮关闭对话框。

添加必要的 Imports 语句或 using 指令

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

  2. 添加以下 Imports 语句 (Visual Basic) 或 using 指令 (c#) 添加到代码文件顶部,如果这些子项尚不存在)。

    Imports System.Net.Http
    Imports System.Net
    Imports System.IO
    
    using System.Net.Http;
    using System.Net;
    using System.IO;
    

创建一个同步应用程序

  1. 在"设计"窗口,MainWindow.xaml,在 MainWindow.xaml.vb 或 MainWindow.xaml.cs 中双击 启动 按钮创建 startButton_Click 事件处理程序。 或者,显示 启动 按钮,选择。属性 窗口的 所选元素的事件处理程序 图标,然后输入 startButton_Click 在 单击 文本框。

  2. 在 MainWindow.xaml.vb 或 MainWindow.xaml.cs 中,将以下代码复制到 startButton_Click主体。

    resultsTextBox.Clear()
    SumPageSizes()
    resultsTextBox.Text &= vbCrLf & "Control returned to startButton_Click."
    
    resultsTextBox.Clear();
    SumPageSizes();
    resultsTextBox.Text += "\r\nControl returned to startButton_Click.";
    

    代码调用驱动应用程序,SumPageSizes,并显示消息的方法,当控件返回 startButton_Click时。

  3. 该同步解决方案中的代码包含以下四个方法:

    • SumPageSizes,从 SetUpURLList 获取网页 URL 列表随后调用 GetURLContents 和 DisplayResults 处理每个 URL。

    • SetUpURLList,使并返回列表 web 地址。

    • GetURLContents,下载每个网站目录并返回目录作为字节数组。

    • DisplayResults,显示中的字节数字节数组的每个 URL 的。

    复制以下四个方法,然后将其粘贴在 MainWindow.xaml.vb 或 MainWindow.xaml.cs 的 startButton_Click 事件处理程序中。

    Private Sub SumPageSizes()
    
        ' Make a list of web addresses. 
        Dim urlList As List(Of String) = SetUpURLList()
    
        Dim total = 0
        For Each url In urlList
            ' GetURLContents returns the contents of url as a byte array. 
            Dim urlContents As Byte() = GetURLContents(url)
    
            DisplayResults(url, urlContents)
    
            ' Update the total.
            total += urlContents.Length
        Next 
    
        ' Display the total count for all of the web addresses.
        resultsTextBox.Text &= String.Format(vbCrLf & vbCrLf & "Total bytes returned:  {0}" & vbCrLf, total)
    End Sub 
    
    
    Private Function SetUpURLList() As List(Of String)
    
        Dim urls = New List(Of String) From
            {
                "https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "https://msdn.microsoft.com/en-us/library/ee256749.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            }
        Return urls
    End Function 
    
    
    Private Function GetURLContents(url As String) As Byte()
    
        ' The downloaded resource ends up in the variable named content. 
        Dim content = New MemoryStream()
    
        ' Initialize an HttpWebRequest for the current URL. 
        Dim webReq = CType(WebRequest.Create(url), HttpWebRequest)
    
        ' Send the request to the Internet resource and wait for 
        ' the response. 
        ' Note: you can't use HttpWebRequest.GetResponse in a Windows Store app. 
        Using response As WebResponse = webReq.GetResponse()
            ' Get the data stream that is associated with the specified URL. 
            Using responseStream As Stream = response.GetResponseStream()
                ' Read the bytes in responseStream and copy them to content.  
                responseStream.CopyTo(content)
            End Using 
        End Using 
    
        ' Return the result as a byte array. 
        Return content.ToArray()
    End Function 
    
    
    Private Sub DisplayResults(url As String, content As Byte())
    
        ' Display the length of each website. The string format  
        ' is designed to be used with a monospaced font, such as 
        ' Lucida Console or Global Monospace. 
        Dim bytes = content.Length
        ' Strip off the "http://". 
        Dim displayURL = url.Replace("http://", "")
        resultsTextBox.Text &= String.Format(vbCrLf & "{0,-58} {1,8}", displayURL, bytes)
    End Sub
    
    private void SumPageSizes()
    {
        // Make a list of web addresses.
        List<string> urlList = SetUpURLList(); 
    
        var total = 0;
        foreach (var url in urlList)
        {
            // GetURLContents returns the contents of url as a byte array. 
            byte[] urlContents = GetURLContents(url);
    
            DisplayResults(url, urlContents);
    
            // Update the total.
            total += urlContents.Length;
        }
    
        // Display the total count for all of the web addresses.
        resultsTextBox.Text += 
            string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);
    }
    
    
    private List<string> SetUpURLList()
    {
        var urls = new List<string> 
        { 
            "https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
            "https://msdn.microsoft.com",
            "https://msdn.microsoft.com/en-us/library/hh290136.aspx",
            "https://msdn.microsoft.com/en-us/library/ee256749.aspx",
            "https://msdn.microsoft.com/en-us/library/hh290138.aspx",
            "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
            "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
            "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
            "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
            "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
        };
        return urls;
    }
    
    
    private byte[] GetURLContents(string url)
    {
        // The downloaded resource ends up in the variable named content. 
        var content = new MemoryStream();
    
        // Initialize an HttpWebRequest for the current URL. 
        var webReq = (HttpWebRequest)WebRequest.Create(url);
    
        // Send the request to the Internet resource and wait for 
        // the response. 
        // Note: you can't use HttpWebRequest.GetResponse in a Windows Store app. 
        using (WebResponse response = webReq.GetResponse())
        {
            // Get the data stream that is associated with the specified URL. 
            using (Stream responseStream = response.GetResponseStream())
            {
                // Read the bytes in responseStream and copy them to content.  
                responseStream.CopyTo(content);
            }
        }
    
        // Return the result as a byte array. 
        return content.ToArray();
    }
    
    
    private void DisplayResults(string url, byte[] content)
    {
        // Display the length of each website. The string format  
        // is designed to be used with a monospaced font, such as 
        // Lucida Console or Global Monospace. 
        var bytes = content.Length;
        // Strip off the "http://".
        var displayURL = url.Replace("http://", "");
        resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);
    }
    

测试同步解决方案

  • 选择 F5 键运行程序,然后选择 启动 按钮。

    类似于下面的输出列表应显示。

    msdn.microsoft.com/library/windows/apps/br211380.aspx        383832
    msdn.microsoft.com                                            33964
    msdn.microsoft.com/en-us/library/hh290136.aspx               225793
    msdn.microsoft.com/en-us/library/ee256749.aspx               143577
    msdn.microsoft.com/en-us/library/hh290138.aspx               237372
    msdn.microsoft.com/en-us/library/hh290140.aspx               128279
    msdn.microsoft.com/en-us/library/dd470362.aspx               157649
    msdn.microsoft.com/en-us/library/aa578028.aspx               204457
    msdn.microsoft.com/en-us/library/ms404677.aspx               176405
    msdn.microsoft.com/en-us/library/ff730837.aspx               143474
    
    Total bytes returned:  1834802
    
    Control returned to startButton_Click.
    

    通知需要几秒钟显示计数。 与此同时,它在等待请求资源下载时,UI 线程阻塞。 因此,在选择 启动 按钮后,将无法移动,最大化,最小化,甚至关闭显示窗口。 这些工作失败,直到字节数开始显示。 如果网站没有响应时,您没有站点失败的表示形式。 等待和关闭程序即使是很困难的。

    与 示例 比较此行为与异步解决方案。

转换 GetURLContents 转换为异步方法

  1. 若要将同步解决方法转换为异步解决方案,则最佳位置开始。GetURLContents,因为对 HttpWebRequest 方法 GetResponse,对 Stream 方法 CopyTo 对于应用程序访问该 web。 .NET framework 允许转换轻松通过提供两个方法的异步版本。

    有关用于 GetURLContents的方法的更多信息,请参见 WebRequest

    备注

    因为您按照本演练中的步骤,多个编译器出现错误。您可以忽略这些问题和继续该演练。

    将 GetURLContents 第三行调用从 GetResponse 为异步操作方法,基于任务的 GetResponseAsync 方法。

    Using response As WebResponse = webReq.GetResponseAsync()
    
    using (WebResponse response = webReq.GetResponseAsync())
    
  2. GetResponseAsync 将返回 Task。 在这种情况下,任务返回变量,TResult,具有类型 WebResponse。 任务是承诺导致实际 WebResponse 对象,则该请求的数据下载之后,并且任务已完成运行。

    从任务若要检索 WebResponse 值,将 Await (Visual Basic) 或 (c#) 等待 运算符应用于调用于 GetResponseAsync,下面的代码显示。

    Using response As WebResponse = Await webReq.GetResponseAsync()
    
    using (WebResponse response = await webReq.GetResponseAsync())
    

    等待运算符挂起当前方法,GetURLContents的执行,直到等待的任务完成。 同时,控件返回到当前方法的调用方。 在此示例中,当前方法是 GetURLContents,因此,调用方是 SumPageSizes。 当任务完成时,将提交的 WebResponse 对象生成,当等待的任务的值并分配给可变 response。

    前面的语句可分为以下两个语句阐明所发生的情况。

    'Dim responseTask As Task(Of WebResponse) = webReq.GetResponseAsync() 
    'Using response As WebResponse = Await responseTask
    
    //Task<WebResponse> responseTask = webReq.GetResponseAsync(); 
    //using (WebResponse response = await responseTask)
    

    为 webReq.GetResponseAsync 的调用返回 Task(Of WebResponse) 或 Task<WebResponse>。 然后等待运算符应用于任务检索 WebResponse 值。

    如果您的异步方法具有不依赖于完成执行的工作,方法可以继续在这两个语句之间的工作,中,调用异步方法以及在等待运算符之前将应用于之后。 有关示例,请参见如何:使用 Async 和 Await 并行发起多个 Web 请求(C# 和 Visual Basic)如何:使用 Task.WhenAll 扩展异步演练(C# 和 Visual Basic)

  3. 由于您添加了在上一步中 Await 或 await 运算符,编译器错误。 运算符在标有 异步 的方法只能使用 (Visual Basic) 或 (c#) 异步 修饰符。 当您重复转换步骤替换调用 CopyTo 通过调用到 CopyToAsync时,请忽略错误。

    • 更改调用 CopyToAsync方法的名称。

    • CopyTo 或 CopyToAsync 方法复制字节为其参数,content和不返回有意义的值。 处于同步版本,为 CopyTo 的调用不返回值的简单语句。 该异步版本,CopyToAsync,返回 Task。 任务对象就象“任务 (失效)”并使该方法等待。 应用 Await 或 await 于调用于 CopyToAsync,下面的代码显示。

      Await responseStream.CopyToAsync(content)
      
      await responseStream.CopyToAsync(content);
      

      前面的语句缩写下面两行代码。

      ' CopyToAsync returns a Task, not a Task<T>. 
      'Dim copyTask As Task = responseStream.CopyToAsync(content) 
      
      ' When copyTask is completed, content contains a copy of 
      ' responseStream. 
      'Await copyTask
      
      // CopyToAsync returns a Task, not a Task<T>. 
      //Task copyTask = responseStream.CopyToAsync(content); 
      
      // When copyTask is completed, content contains a copy of 
      // responseStream. 
      //await copyTask;
      
  4. 在 GetURLContents 仍然要执行的只是调整方法签名。 在标有 异步 的方法只能使用 Await 或 await 运算符 (Visual Basic) 或 (c#) 异步 修饰符。 添加修饰符标记方法作为 异步方法,即,下面的代码所示。

    Private Async Function GetURLContents(url As String) As Byte()
    
    private async byte[] GetURLContents(string url)
    
  5. 异步方法的返回类型只能 TaskTask或 void 在 c# 中。 在 Visual Basic 中,返回 Task 或 Task(Of T)的方法必须是 Function,或者该方法必须是 Sub。 通常,Sub 方法 (Visual Basic) 或 void (c#) 的返回类型在异步事件处理程序仅使用,需要 Sub 或 void。 在某些情况下,您使用 Task(T),如果返回类型 T 的值的完整方法具有 返回返回 语句以及使用 Task,如果已完成方法不返回有意义的值。 可以将 Task 返回类型作为含义“任务 (失效)”。

    有关更多信息,请参见异步返回类型(C# 和 Visual Basic)

    方法 GetURLContents 具有返回语句,因此,该语句返回字节数组。 因此,异步版本的返回类型为任务 (of T),T 为字节数组。 在方法签名中进行以下更改:

    • 返回类型更改 Task(Of Byte()) (Visual Basic) 或 Task<byte[]> (c#)。

    • 按照约定,异步方法以“"结尾的名称,”,因此可对方法 GetURLContentsAsync重命名。

    下面的代码显示这些更改。

    Private Async Function GetURLContentsAsync(url As String) As Task(Of Byte())
    
    private async Task<byte[]> GetURLContentsAsync(string url)
    

    少数更改,GetURLContents 转换为异步方法完成。

转换 SumPageSizes 转换为异步方法

  1. 重复上述过程的步骤 SumPageSizes的。 首先,将调用到 GetURLContents 到异步调用。

    • 从更改 GetURLContents 调用 GetURLContentsAsync方法的名称,因此,如果您尚未。

    • 应用 Await 或 await 于 GetURLContentsAsync 返回获取字节数组值的任务。

    下面的代码显示这些更改。

    Dim urlContents As Byte() = Await GetURLContentsAsync(url)
    
    byte[] urlContents = await GetURLContentsAsync(url);
    

    前面的分配缩写下面两行代码。

    ' GetURLContentsAsync returns a task. At completion, the task 
    ' produces a byte array. 
    'Dim getContentsTask As Task(Of Byte()) = GetURLContentsAsync(url) 
    'Dim urlContents As Byte() = Await getContentsTask
    
    // GetURLContentsAsync returns a Task<T>. At completion, the task 
    // produces a byte array. 
    //Task<byte[]> getContentsTask = GetURLContentsAsync(url); 
    //byte[] urlContents = await getContentsTask;
    
  2. 在方法的签名进行以下更改:

    • 标记与 Async 或 async 修饰符的方法。

    • 添加“Async”到方法的名称。

    • 因为 SumPageSizesAsync 不返回 T. 的,一个值不任务返回变量,T,这次。 (该方法没有 Return 或 return 语句)。但是,该方法必须返回 Task awaitable。 因此,进行下列更改:

      • 在 Visual Basic 中,从 Sub 将控制传递到 Function。 函数的返回类型为 Task。

      • 在 c# 中,从 void 请更改方法的返回类型为 Task。

    下面的代码显示这些更改。

    Private Async Function SumPageSizesAsync() As Task
    
    private async Task SumPageSizesAsync()
    

    SumPageSizes 转换为 SumPageSizesAsync 完成。

转换 startButton_Click 转换为异步方法

  1. 在事件处理程序,因此,如果不这样,已执行从 SumPageSizes 将调用的方法的名称。SumPageSizesAsync。

  2. 由于 SumPageSizesAsync 是异步方法,请更改该事件处理程序中的代码等待结果。

    为 SumPageSizesAsync 的调用反射调用 GetURLContentsAsync的 CopyToAsync。 调用返回 Task,而不是 Task(T)。

    通过使用语句或两个语句,在前面的过程中,您可以将调用。 下面的代码显示这些更改。

    '' One-step async call.
    Await SumPageSizesAsync()
    
    ' Two-step async call. 
    'Dim sumTask As Task = SumPageSizesAsync() 
    'Await sumTask
    
    // One-step async call.
    await SumPageSizesAsync();
    
    // Two-step async call. 
    //Task sumTask = SumPageSizesAsync(); 
    //await sumTask;
    
  3. 若要防止意外地重新输入操作,请添加以下语句。startButton_Click 顶部禁用 启动 按钮。

    ' Disable the button until the operation is complete.
    startButton.IsEnabled = False
    
    // Disable the button until the operation is complete.
    startButton.IsEnabled = false;
    

    可以重新启用按钮在事件处理程序的末尾。

    ' Reenable the button in case you want to run the operation again.
    startButton.IsEnabled = True
    
    // Reenable the button in case you want to run the operation again.
    startButton.IsEnabled = true;
    

    有关进入的更多信息,请参见 处理异步应用程序中的重入(C# 和 Visual Basic)

  4. 最后,添加 Async 或 async 修饰符到声明,以便事件处理程序可以等待 SumPagSizesAsync。

    Async Sub startButton_Click(sender As Object, e As RoutedEventArgs) Handles startButton.Click
    
    private async void startButton_Click(object sender, RoutedEventArgs e)
    

    通常,不更改事件处理程序的名称。 因为事件处理程序必须返回在 C# 中 void 或是 Visual Basic,的 Sub 程序返回类型不更改为 Task。 因此,使用 Task的返回类型。

    项目的变换从同步到异步操作完成的。

研究异步解决方案

  1. 选择 F5 键运行程序,然后选择 启动 按钮。

  2. 类似于该同步解决方案的输出的输出应显示。 但是,请注意以下差异。

    • 过程完成后,结果都同时不会发生。 例如,两个程序在 startButton_Click 包含清除文本框中的一行。 此语句的目的是清除之间文本框运行,如果选择 启动 按钮第二次,因此,在一个结果集出现后。 处于同步版本,清除文本框中,计算第二次之前显示,那么,当下载时完成,并且 UI 线程可以自由执行其他工作。 在异步版本,因此,在选择 启动 按钮后,文本框中清除。

    • 最重要的是,UI 线程未阻止在下载过程。 当 web 资源下载,计数,并显示时,可以移动或调整窗口的大小。 如果其中一个网站速度或不响应,可以通过选择 关闭 按钮取消了操作 (红色字段的 x 在右上角)。

使用 .NET framework 方法替换方法 GetURLContentsAsync

  1. .NET framework 4.5 提供了可以使用的许多异步方法。 其中的一个,HttpClient 方法 GetByteArrayAsync(String),执行所出于本演练的需要。 您可以使用它而不是在早期的过程中创建的 GetURLContentsAsync 方法。

    第一步是创建 HttpClient 对象在方法 SumPageSizesAsync。 在方法的开头添加以下声明。

    ' Declare an HttpClient object and increase the buffer size. The 
    ' default buffer size is 65,536. 
    Dim client As HttpClient =
        New HttpClient() With {.MaxResponseContentBufferSize = 1000000}
    
    // Declare an HttpClient object and increase the buffer size. The 
    // default buffer size is 65,536.
    HttpClient client =
        new HttpClient() { MaxResponseContentBufferSize = 1000000 };
    
  2. 在 SumPageSizesAsync, 替换调用您的 GetURLContentsAsync 方法将调用 HttpClient 方法。

    Dim urlContents As Byte() = Await client.GetByteArrayAsync(url)
    
    byte[] urlContents = await client.GetByteArrayAsync(url);               
    
  3. 移除或注释掉您编写的 GetURLContentsAsync 方法。

  4. 选择 F5 键运行程序,然后选择 启动 按钮。

    该项的此版本行为应当对“研究异步解决方案”过程描述的行为,但在不太从您的工作。

示例

下面的代码包含转换的完整示例从同步到异步解决方案使用您编写的异步 GetURLContentsAsync 方法。 通知它完全类似原,同步解决方案。

' Add the following Imports statements, and add a reference for System.Net.Http. 
Imports System.Net.Http
Imports System.Net
Imports System.IO

Class MainWindow

    Async Sub startButton_Click(sender As Object, e As RoutedEventArgs) Handles startButton.Click

        ' Disable the button until the operation is complete.
        startButton.IsEnabled = False

        resultsTextBox.Clear()

        '' One-step async call.
        Await SumPageSizesAsync()

        ' Two-step async call. 
        'Dim sumTask As Task = SumPageSizesAsync() 
        'Await sumTask

        resultsTextBox.Text &= vbCrLf & "Control returned to button1_Click." 

        ' Reenable the button in case you want to run the operation again.
        startButton.IsEnabled = True 
    End Sub 


    Private Async Function SumPageSizesAsync() As Task

        ' Make a list of web addresses. 
        Dim urlList As List(Of String) = SetUpURLList()

        Dim total = 0
        For Each url In urlList
            Dim urlContents As Byte() = Await GetURLContentsAsync(url)

            ' The previous line abbreviates the following two assignment statements. 

            ' GetURLContentsAsync returns a task. At completion, the task 
            ' produces a byte array. 
            'Dim getContentsTask As Task(Of Byte()) = GetURLContentsAsync(url) 
            'Dim urlContents As Byte() = Await getContentsTask

            DisplayResults(url, urlContents)

            ' Update the total.
            total += urlContents.Length
        Next 

        ' Display the total count for all of the websites.
        resultsTextBox.Text &= String.Format(vbCrLf & vbCrLf &
                                             "Total bytes returned:  {0}" & vbCrLf, total)
    End Function 


    Private Function SetUpURLList() As List(Of String)

        Dim urls = New List(Of String) From
            {
                "https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "https://msdn.microsoft.com/en-us/library/ee256749.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            }
        Return urls
    End Function 


    Private Async Function GetURLContentsAsync(url As String) As Task(Of Byte())

        ' The downloaded resource ends up in the variable named content. 
        Dim content = New MemoryStream()

        ' Initialize an HttpWebRequest for the current URL. 
        Dim webReq = CType(WebRequest.Create(url), HttpWebRequest)

        ' Send the request to the Internet resource and wait for 
        ' the response. 
        Using response As WebResponse = Await webReq.GetResponseAsync()

            ' The previous statement abbreviates the following two statements. 

            'Dim responseTask As Task(Of WebResponse) = webReq.GetResponseAsync() 
            'Using response As WebResponse = Await responseTask 

            ' Get the data stream that is associated with the specified URL. 
            Using responseStream As Stream = response.GetResponseStream()
                ' Read the bytes in responseStream and copy them to content.  
                Await responseStream.CopyToAsync(content)

                ' The previous statement abbreviates the following two statements. 

                ' CopyToAsync returns a Task, not a Task<T>. 
                'Dim copyTask As Task = responseStream.CopyToAsync(content) 

                ' When copyTask is completed, content contains a copy of 
                ' responseStream. 
                'Await copyTask 
            End Using 
        End Using 

        ' Return the result as a byte array. 
        Return content.ToArray()
    End Function 


    Private Sub DisplayResults(url As String, content As Byte())

        ' Display the length of each website. The string format  
        ' is designed to be used with a monospaced font, such as 
        ' Lucida Console or Global Monospace. 
        Dim bytes = content.Length
        ' Strip off the "http://". 
        Dim displayURL = url.Replace("http://", "")
        resultsTextBox.Text &= String.Format(vbCrLf & "{0,-58} {1,8}", displayURL, bytes)
    End Sub 

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 the following using directives, and add a reference for System.Net.Http. 
using System.Net.Http;
using System.IO;
using System.Net;

namespace AsyncExampleWPF
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            // Disable the button until the operation is complete.
            startButton.IsEnabled = false;

            resultsTextBox.Clear();

            // One-step async call.
            await SumPageSizesAsync();

            // Two-step async call. 
            //Task sumTask = SumPageSizesAsync(); 
            //await sumTask;

            resultsTextBox.Text += "\r\nControl returned to startButton_Click.\r\n";

            // Reenable the button in case you want to run the operation again.
            startButton.IsEnabled = true;
        }


        private async Task SumPageSizesAsync()
        {
            // Make a list of web addresses.
            List<string> urlList = SetUpURLList();

            var total = 0;

            foreach (var url in urlList)
            {
                byte[] urlContents = await GetURLContentsAsync(url);

                // The previous line abbreviates the following two assignment statements. 

                // GetURLContentsAsync returns a Task<T>. At completion, the task 
                // produces a byte array. 
                //Task<byte[]> getContentsTask = GetURLContentsAsync(url); 
                //byte[] urlContents = await getContentsTask;

                DisplayResults(url, urlContents);

                // Update the total.          
                total += urlContents.Length;
            }
            // Display the total count for all of the websites.
            resultsTextBox.Text +=
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);
        }


        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "https://msdn.microsoft.com/en-us/library/ee256749.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            };
            return urls;
        }


        private async Task<byte[]> GetURLContentsAsync(string url)
        {
            // The downloaded resource ends up in the variable named content. 
            var content = new MemoryStream();

            // Initialize an HttpWebRequest for the current URL. 
            var webReq = (HttpWebRequest)WebRequest.Create(url);

            // Send the request to the Internet resource and wait for 
            // the response.                 
            using (WebResponse response = await webReq.GetResponseAsync())

            // The previous statement abbreviates the following two statements. 

            //Task<WebResponse> responseTask = webReq.GetResponseAsync(); 
            //using (WebResponse response = await responseTask)
            {
                // Get the data stream that is associated with the specified url. 
                using (Stream responseStream = response.GetResponseStream())
                {
                    // Read the bytes in responseStream and copy them to content. 
                    await responseStream.CopyToAsync(content);

                    // The previous statement abbreviates the following two statements. 

                    // CopyToAsync returns a Task, not a Task<T>. 
                    //Task copyTask = responseStream.CopyToAsync(content); 

                    // When copyTask is completed, content contains a copy of 
                    // responseStream. 
                    //await copyTask;
                }
            }
            // Return the result as a byte array. 
            return content.ToArray();
        }


        private void DisplayResults(string url, byte[] content)
        {
            // Display the length of each website. The string format  
            // is designed to be used with a monospaced font, such as 
            // Lucida Console or Global Monospace. 
            var bytes = content.Length;
            // Strip off the "http://".
            var displayURL = url.Replace("http://", "");
            resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);
        }
    }
}

下面的代码包含使用 HttpClient 解决方案,GetByteArrayAsync的完整示例。

' Add the following Imports statements, and add a reference for System.Net.Http. 
Imports System.Net.Http
Imports System.Net
Imports System.IO

Class MainWindow

    Async Sub startButton_Click(sender As Object, e As RoutedEventArgs) Handles startButton.Click

        resultsTextBox.Clear()

        ' Disable the button until the operation is complete.
        startButton.IsEnabled = False 

        ' One-step async call.
        Await SumPageSizesAsync()

        '' Two-step async call. 
        'Dim sumTask As Task = SumPageSizesAsync() 
        'Await sumTask

        resultsTextBox.Text &= vbCrLf & "Control returned to button1_Click." 

        ' Reenable the button in case you want to run the operation again.
        startButton.IsEnabled = True 
    End Sub 


    Private Async Function SumPageSizesAsync() As Task

        ' Declare an HttpClient object and increase the buffer size. The 
        ' default buffer size is 65,536. 
        Dim client As HttpClient =
            New HttpClient() With {.MaxResponseContentBufferSize = 1000000}

        ' Make a list of web addresses. 
        Dim urlList As List(Of String) = SetUpURLList()

        Dim total = 0
        For Each url In urlList
            ' GetByteArrayAsync returns a task. At completion, the task 
            ' produces a byte array. 
            Dim urlContents As Byte() = Await client.GetByteArrayAsync(url)

            ' The following two lines can replace the previous assignment statement. 
            'Dim getContentsTask As Task(Of Byte()) = client.GetByteArrayAsync(url) 
            'Dim urlContents As Byte() = Await getContentsTask

            DisplayResults(url, urlContents)

            ' Update the total.
            total += urlContents.Length
        Next 

        ' Display the total count for all of the websites.
        resultsTextBox.Text &= String.Format(vbCrLf & vbCrLf &
                                             "Total bytes returned:  {0}" & vbCrLf, total)
    End Function 


    Private Function SetUpURLList() As List(Of String)

        Dim urls = New List(Of String) From
            {
                "https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "https://msdn.microsoft.com/en-us/library/ee256749.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            }
        Return urls
    End Function 


    Private Sub DisplayResults(url As String, content As Byte())

        ' Display the length of each website. The string format  
        ' is designed to be used with a monospaced font, such as 
        ' Lucida Console or Global Monospace. 
        Dim bytes = content.Length
        ' Strip off the "http://". 
        Dim displayURL = url.Replace("http://", "")
        resultsTextBox.Text &= String.Format(vbCrLf & "{0,-58} {1,8}", displayURL, bytes)
    End Sub 

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 the following using directives, and add a reference for System.Net.Http. 
using System.Net.Http;
using System.IO;
using System.Net;


namespace AsyncExampleWPF
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            resultsTextBox.Clear();

            // Disable the button until the operation is complete.
            startButton.IsEnabled = false;

            // One-step async call.
            await SumPageSizesAsync();

            //// Two-step async call. 
            //Task sumTask = SumPageSizesAsync(); 
            //await sumTask;

            resultsTextBox.Text += "\r\nControl returned to startButton_Click.\r\n";

            // Reenable the button in case you want to run the operation again.
            startButton.IsEnabled = true;
        }


        private async Task SumPageSizesAsync()
        {
            // Declare an HttpClient object and increase the buffer size. The 
            // default buffer size is 65,536.
            HttpClient client =
                new HttpClient() { MaxResponseContentBufferSize = 1000000 };

            // Make a list of web addresses.
            List<string> urlList = SetUpURLList();

            var total = 0;

            foreach (var url in urlList)
            {
                // GetByteArrayAsync returns a task. At completion, the task 
                // produces a byte array. 
                byte[] urlContents = await client.GetByteArrayAsync(url);               

                // The following two lines can replace the previous assignment statement. 
                //Task<byte[]> getContentsTask = client.GetByteArrayAsync(url); 
                //byte[] urlContents = await getContentsTask;

                DisplayResults(url, urlContents);

                // Update the total.
                total += urlContents.Length;
            }

            // Display the total count for all of the websites.
            resultsTextBox.Text +=
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);
        }


        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "https://msdn.microsoft.com/en-us/library/ee256749.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            };
            return urls;
        }


        private void DisplayResults(string url, byte[] content)
        {
            // Display the length of each website. The string format  
            // is designed to be used with a monospaced font, such as 
            // Lucida Console or Global Monospace. 
            var bytes = content.Length;
            // Strip off the "http://".
            var displayURL = url.Replace("http://", "");
            resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);
        }
    }
}

请参见

任务

如何:使用 Task.WhenAll 扩展异步演练(C# 和 Visual Basic)

如何:使用 Async 和 Await 并行发起多个 Web 请求(C# 和 Visual Basic)

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

参考

async(C# 参考)

await(C# 参考)

Await 运算符 (Visual Basic)

Async (Visual Basic)

概念

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

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

使用 Async 以进行文件访问(C# 和 Visual Basic)

其他资源

Async 示例:访问 Web 演练 (C# 和 Visual Basic)

基于任务的异步编程 (非常接头)

快速入门:使用异步编程的时间运算符