演练:使用 async 和 await 访问 Web (C#)Walkthrough: Accessing the Web by Using async and await (C#)

使用 async/await 功能可以更轻松直观地编写异步程序。You can write asynchronous programs more easily and intuitively by using async/await features. 你可以编写类似于同步代码的异步代码,并让编译器处理异步代码通常需要的疑难回调函数和延续。You can write asynchronous code that looks like synchronous code and let the compiler handle the difficult callback functions and continuations that asynchronous code usually entails.

有关 Async 功能的详细信息,请参阅使用 Async 和 Await 的异步编程 (C#)For more information about the Async feature, see Asynchronous Programming with async and await (C#).

本演练从对网站列表中的字节数进行求和的同步 Windows Presentation Foundation (WPF) 应用程序入手,This walkthrough starts with a synchronous Windows Presentation Foundation (WPF) application that sums the number of bytes in a list of websites. 然后使用新功能将该应用程序转换为异步解决方案。The walkthrough then converts the application to an asynchronous solution by using the new features.

如果不想自行生成应用,可以下载异步示例:访问 Web 演练(C# 和 Visual Basic)If you don't want to build the applications yourself, you can download Async Sample: Accessing the Web Walkthrough (C# and Visual Basic).

备注

若要运行该示例,计算机上必须安装有 Visual Studio 2012 或更高版本和 .NET Framework 4.5 或更高版本。To run the examples, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

创建 WPF 应用程序Create a WPF application

  1. 启动 Visual Studio。Start Visual Studio.

  2. 在菜单栏上,依次选择“文件” > “新建” > “项目”。On the menu bar, choose File > New > Project.

    “新建项目” 对话框随即打开。The New Project dialog box opens.

  3. 在“已安装的模板”窗格中,选择“Visual C#”,然后从项目类型列表选择“WPF 应用程序”。In the Installed Templates pane, choose Visual C#, and then choose WPF Application from the list of project types.

  4. 在“名称”文本框中,输入 AsyncExampleWPF,然后选择“确定”按钮。In the Name text box, enter AsyncExampleWPF, and then choose the OK button.

    新项目将出现在“解决方案资源管理器”中。The new project appears in Solution Explorer.

设计简单的 WPF MainWindowDesign a simple WPF MainWindow

  1. 在 Visual Studio 代码编辑器中,选择 “MainWindow.xaml” 选项卡。In the Visual Studio Code Editor, choose the MainWindow.xaml tab.

  2. 如果“工具箱”窗口不可见,则打开“视图”菜单,然后选择“工具箱”。If the Toolbox window isn’t visible, open the View menu, and then choose Toolbox.

  3. 向“MainWindow”窗口添加一个“Button”控件和一个“TextBox”控件。Add a Button control and a TextBox control to the MainWindow window.

  4. 突出显示“TextBox”控件,在“属性”窗口中,设置下列值:Highlight the TextBox control and, in the Properties window, set the following values:

    • 将“名称”属性设置为 resultsTextBoxSet the Name property to resultsTextBox.

    • 将“高度”属性设置为 250。Set the Height property to 250.

    • 将“宽度”属性设置为 500。Set the Width property to 500.

    • 在“文本”选项卡中,指定等宽字体,例如 Lucida Console 或 Global Monospace。On the Text tab, specify a monospaced font, such as Lucida Console or Global Monospace.

  5. 突出显示“Button”控件,在“属性”窗口中,设置下列值:Highlight the Button control and, in the Properties window, set the following values:

    • 将“名称”属性设置为 startButtonSet the Name property to startButton.

    • 将“内容”属性的值从“Button”更改为“Start”。Change the value of the Content property from Button to Start.

  6. 确定文本框和按钮的位置,以便它们都在“MainWindow”窗口中显示。Position the text box and the button so that both appear in the MainWindow window.

    有关 WPF XAML 设计器的详细信息,请参阅使用 XAML 设计器创建 UIFor more information about the WPF XAML Designer, see Creating a UI by using XAML Designer.

添加引用Add a reference

  1. 在“解决方案资源管理器”中,突出显示项目的名称。In Solution Explorer, highlight your project's name.

  2. 在菜单栏上,选择“项目” > “添加引用”。On the menu bar, choose Project > Add Reference.

    此时将显示“引用管理器”对话框。The Reference Manager dialog box appears.

  3. 在对话框顶部,验证项目是否以 .NET Framework 4.5 或更高版本为目标。At the top of the dialog box, verify that your project is targeting the .NET Framework 4.5 or higher.

  4. 在“程序集”类别中,选择“Framework”(如果尚未选择它)。In the Assemblies category, choose Framework if it isn’t already chosen.

  5. 在名称列表中,选中“System.Net.Http”复选框。In the list of names, select the System.Net.Http check box.

  6. 选择“确定”按钮关闭对话框。Choose the OK button to close the dialog box.

添加必要的 using 指令Add necessary using directives

  1. 在“解决方案资源管理器”中,打开 MainWindow.xaml.cs 的快捷菜单,然后选择“查看代码”。In Solution Explorer, open the shortcut menu for MainWindow.xaml.cs, and then choose View Code.

  2. 将下列 using 指令添加到代码文件的顶部(如果它们尚不存在)。Add the following using directives at the top of the code file if they’re not already present.

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

创建同步应用Create a synchronous app

  1. 在设计窗口 MainWindow.xaml 中,双击“启动”按钮以在 MainWindow.xaml.cs 中创建 startButton_Click 事件处理程序。In the design window, MainWindow.xaml, double-click the Start button to create the startButton_Click event handler in MainWindow.xaml.cs.

  2. 在 MainWindow.xaml.cs 中,将下列代码复制到 startButton_Click 的正文中:In MainWindow.xaml.cs, copy the following code into the body of startButton_Click:

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

    代码调用驱动应用程序 SumPageSizes 的方法,并在控件返回到 startButton_Click 时显示一条消息。The code calls the method that drives the application, SumPageSizes, and displays a message when control returns to startButton_Click.

  3. 该同步解决方案的代码包含以下四个方法:The code for the synchronous solution contains the following four methods:

    • SumPageSizes,从 SetUpURLList 获取网页 URL 列表并随后调用 GetURLContentsDisplayResults 以处理每个 URL。SumPageSizes, which gets a list of webpage URLs from SetUpURLList and then calls GetURLContents and DisplayResults to process each URL.

    • SetUpURLList,生成并返回 Web 地址列表。SetUpURLList, which makes and returns a list of web addresses.

    • GetURLContents,下载每个网站的内容并将内容作为字节数组返回。GetURLContents, which downloads the contents of each website and returns the contents as a byte array.

    • DisplayResults,显示每个 URL 的字节数组中的字节数。DisplayResults, which displays the number of bytes in the byte array for each URL.

    复制以下四个方法,然后将它们粘贴在 MainWindow.xaml.cs 中的 startButton_Click 事件处理程序下:Copy the following four methods, and then paste them under the startButton_Click event handler in MainWindow.xaml.cs:

    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 += $"\r\n\r\nTotal bytes returned:  {total}\r\n";
    }
    
    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/library/hh290136.aspx",
            "https://msdn.microsoft.com/library/ee256749.aspx",
            "https://msdn.microsoft.com/library/hh290138.aspx",
            "https://msdn.microsoft.com/library/hh290140.aspx",
            "https://msdn.microsoft.com/library/dd470362.aspx",
            "https://msdn.microsoft.com/library/aa578028.aspx",
            "https://msdn.microsoft.com/library/ms404677.aspx",
            "https://msdn.microsoft.com/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 "https://".
        var displayURL = url.Replace("https://", "");
        resultsTextBox.Text += $"\n{displayURL,-58} {bytes,8}";
    }
    

测试同步解决方案Test the synchronous solution

按 F5 键运行程序,然后选择“启动”按钮。Choose the F5 key to run the program, and then choose the Start button.

此时应显示类似于以下列表的输出:Output that resembles the following list should appear:

msdn.microsoft.com/library/windows/apps/br211380.aspx        383832
msdn.microsoft.com                                            33964
msdn.microsoft.com/library/hh290136.aspx               225793
msdn.microsoft.com/library/ee256749.aspx               143577
msdn.microsoft.com/library/hh290138.aspx               237372
msdn.microsoft.com/library/hh290140.aspx               128279
msdn.microsoft.com/library/dd470362.aspx               157649
msdn.microsoft.com/library/aa578028.aspx               204457
msdn.microsoft.com/library/ms404677.aspx               176405
msdn.microsoft.com/library/ff730837.aspx               143474

Total bytes returned:  1834802

Control returned to startButton_Click.

请注意,显示计数需要几秒钟时间。Notice that it takes a few seconds to display the counts. 与此同时,在等待请求的资源下载时,UI 线程处于被阻止状态。During that time, the UI thread is blocked while it waits for requested resources to download. 因此,选择“启动”按钮后,将无法移动、最大化、最小化显示窗口,甚至也无法关闭显示窗口。As a result, you can't move, maximize, minimize, or even close the display window after you choose the Start button. 在字节计数开始显示之前,这些操作都会失败。These efforts fail until the byte counts start to appear. 如果网站没有响应,将不会指示哪个网站失败。If a website isn’t responding, you have no indication of which site failed. 甚至停止等待和关闭程序也会很困难。It is difficult even to stop waiting and close the program.

将 GetURLContents 转换为异步方法Convert GetURLContents to an asynchronous method

  1. 要将同步解决方案转换为异步解决方案,最佳着手点在 GetURLContents 中,因为对 HttpWebRequest 方法 GetResponse 的调用以及对 Stream 方法 CopyTo 的调用是应用程序访问 Web 的位置。To convert the synchronous solution to an asynchronous solution, the best place to start is in GetURLContents because the calls to the HttpWebRequest method GetResponse and to the Stream method CopyTo are where the application accesses the web. .NET Framework 提供两种方法的异步版本,这让转换变得轻松。The .NET Framework makes the conversion easy by supplying asynchronous versions of both methods.

    有关 GetURLContents 中使用的方法的详细信息,请参阅 WebRequestFor more information about the methods that are used in GetURLContents, see WebRequest.

    备注

    在你按照本演练中的步骤进行操作的过程中,将出现多个编译器错误。As you follow the steps in this walkthrough, several compiler errors appear. 你可以忽略这些错误并继续演练。You can ignore them and continue with the walkthrough.

    将在 GetURLContents 的第三行中调用的方法从 GetResponse 更改为基于任务的异步 GetResponseAsync 方法。Change the method that's called in the third line of GetURLContents from GetResponse to the asynchronous, task-based GetResponseAsync method.

    using (WebResponse response = webReq.GetResponseAsync())
    
  2. GetResponseAsync 返回 Task<TResult>GetResponseAsync returns a Task<TResult>. 在这种情况下,任务返回变量 TResult 具有类型 WebResponseIn this case, the task return variable, TResult, has type WebResponse. 该任务是在请求的任务已下载且任务已完成运行后,生成实际 WebResponse 对象的承诺。The task is a promise to produce an actual WebResponse object after the requested data has been downloaded and the task has run to completion.

    要从任务检索 WebResponse 值,请将 await 运算符应用到对 GetResponseAsync 的调用,如下列代码所示。To retrieve the WebResponse value from the task, apply an await operator to the call to GetResponseAsync, as the following code shows.

    using (WebResponse response = await webReq.GetResponseAsync())
    

    await 运算符将当前方法 GetURLContents 的执行挂起,直到完成等待的任务为止。The await operator suspends the execution of the current method, GetURLContents, until the awaited task is complete. 同时,控制权返回给当前方法的调用方。In the meantime, control returns to the caller of the current method. 在此示例中,当前方法是 GetURLContents,调用方是 SumPageSizesIn this example, the current method is GetURLContents, and the caller is SumPageSizes. 任务完成时,承诺的 WebResponse 对象作为等待的任务的值生成,并分配给变量 responseWhen the task is finished, the promised WebResponse object is produced as the value of the awaited task and assigned to the variable response.

    上一条语句可以分为以下两条语句,以阐明所发生的情况。The previous statement can be separated into the following two statements to clarify what happens.

    //Task<WebResponse> responseTask = webReq.GetResponseAsync();
    //using (WebResponse response = await responseTask)
    

    webReq.GetResponseAsync 的调用返回 Task(Of WebResponse)Task<WebResponse>The call to webReq.GetResponseAsync returns a Task(Of WebResponse) or Task<WebResponse>. 然后,await 运算符将应用于任务以检索 WebResponse 值。Then an await operator is applied to the task to retrieve the WebResponse value.

    如果你的异步方法需要完成不依赖于任务的完成的工作,则在调用异步方法之后及应用 await 运算符之前的这段时间,该方法可以在这两个语句之间继续完成该工作。If your async method has work to do that doesn’t depend on the completion of the task, the method can continue with that work between these two statements, after the call to the async method and before the await operator is applied. 相关示例,请参阅如何:使用 async 和 await 并行发出多个 Web 请求 (C#) 以及操作说明:使用 Task.WhenAll 扩展异步演练 (C#)For examples, see How to: Make Multiple Web Requests in Parallel by Using async and await (C#) and How to: Extend the async Walkthrough by Using Task.WhenAll (C#).

  3. 因为在上一步中添加了 await 运算符,所以会发生编译器错误。Because you added the await operator in the previous step, a compiler error occurs. 该运算符仅可在使用 async 修饰符标记的方法中使用。The operator can be used only in methods that are marked with the async modifier. 当你重复转换步骤以使用对 CopyToAsync 的调用替换对 CopyTo 的调用时,请忽略该错误。Ignore the error while you repeat the conversion steps to replace the call to CopyTo with a call to CopyToAsync.

    • 更改被调用到 CopyToAsync 的方法的名称。Change the name of the method that’s called to CopyToAsync.

    • CopyToCopyToAsync 方法复制字节到其参数 content,并且不返回有意义的值。The CopyTo or CopyToAsync method copies bytes to its argument, content, and doesn’t return a meaningful value. 在同步版本中,对 CopyTo 的调用是不返回值的简单语句。In the synchronous version, the call to CopyTo is a simple statement that doesn't return a value. 异步版本 CopyToAsync 返回 TaskThe asynchronous version, CopyToAsync, returns a Task. 任务函数类似“Task(void)”,并让该方法能够等待。The task functions like "Task(void)" and enables the method to be awaited. 应用 Awaitawait 到对 CopyToAsync 的调用,如下列代码所示。Apply Await or await to the call to CopyToAsync, as the following code shows.

      await responseStream.CopyToAsync(content);
      

      上一条语句缩写以下两行代码。The previous statement abbreviates the following two lines of code.

      // 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 中仍需要完成的操作是调整方法签名。All that remains to be done in GetURLContents is to adjust the method signature. 仅可在使用 async 修饰符标记的方法中使用 await 运算符。You can use the await operator only in methods that are marked with the async modifier. 添加修饰符以将方法标记为异步方法,如下列代码所示。Add the modifier to mark the method as an async method, as the following code shows.

    private async byte[] GetURLContents(string url)
    
  5. 异步方法的返回类型在 C# 中只能为 TaskTask<TResult>voidThe return type of an async method can only be Task, Task<TResult>, or void in C#. 通常情况下,void 的返回类型仅可在异步事件处理程序中使用,其中需要 voidTypically, a return type of void is used only in an async event handler, where void is required. 在其他情况下,如果完成的方法具有返回 T 类型的值的 return 语句,则使用 Task(T);如果完成的方法不返回有意义的值,则使用 TaskIn other cases, you use Task(T) if the completed method has a return statement that returns a value of type T, and you use Task if the completed method doesn’t return a meaningful value. 可以将 Task 返回类型视为表示“Task(void)”。You can think of the Task return type as meaning "Task(void)."

    有关详细信息,请参阅异步返回类型 (C#)For more information, see Async Return Types (C#).

    方法 GetURLContents 具有 return 语句,且该语句返回字节数组。Method GetURLContents has a return statement, and the statement returns a byte array. 因此,异步版本的返回类型为 Task(T),其中 T 为字节数组。Therefore, the return type of the async version is Task(T), where T is a byte array. 在方法签名中进行下列更改:Make the following changes in the method signature:

    • 将返回类型更改为 Task<byte[]>Change the return type to Task<byte[]>.

    • 按照约定,异步方法的名称以“Async”结尾,因此,请重命名方法 GetURLContentsAsyncBy convention, asynchronous methods have names that end in "Async," so rename the method GetURLContentsAsync.

    下列代码显示这些更改。The following code shows these changes.

    private async Task<byte[]> GetURLContentsAsync(string url)
    

    进行这几处更改后,GetURLContents 到异步方法的转换完成。With those few changes, the conversion of GetURLContents to an asynchronous method is complete.

将 SumPageSizes 转换为异步方法Convert SumPageSizes to an asynchronous method

  1. SumPageSizes 重复之前过程中的步骤。Repeat the steps from the previous procedure for SumPageSizes. 首先,将对 GetURLContents 的调用更改为异步调用。First, change the call to GetURLContents to an asynchronous call.

    • 将调用的方法的名称从 GetURLContents 更改为 GetURLContentsAsync(如果尚未执行此操作)。Change the name of the method that’s called from GetURLContents to GetURLContentsAsync, if you haven't already done so.

    • await 应用到 GetURLContentsAsync 返回的任务,以便获取字节数组值。Apply await to the task that GetURLContentsAsync returns to obtain the byte array value.

    下列代码显示这些更改。The following code shows these changes.

    byte[] urlContents = await GetURLContentsAsync(url);
    

    上一个分配缩写以下两行代码。The previous assignment abbreviates the following two lines of code.

    // GetURLContentsAsync returns a Task<T>. At completion, the task
    // produces a byte array.
    //Task<byte[]> getContentsTask = GetURLContentsAsync(url);
    //byte[] urlContents = await getContentsTask;
    
  2. 在方法签名中进行下列更改:Make the following changes in the method's signature:

    • 使用 async 修饰符标记方法。Mark the method with the async modifier.

    • 将“Async”添加到方法名称。Add "Async" to the method name.

    • 这一次没有任务返回变量 T,因为 SumPageSizesAsync 不返回 T 的值。(该方法没有 return 语句。)但是,该方法必须返回 Task 才能进行等待。There is no task return variable, T, this time because SumPageSizesAsync doesn’t return a value for T. (The method has no return statement.) However, the method must return a Task to be awaitable. 因此,将该方法的返回类型从 void 更改为 TaskTherefore, change the return type of the method from void to Task.

    下列代码显示这些更改。The following code shows these changes.

    private async Task SumPageSizesAsync()
    

    SumPageSizesSumPageSizesAsync 的转换完成。The conversion of SumPageSizes to SumPageSizesAsync is complete.

将 startButton_Click 转换为异步方法Convert startButton_Click to an asynchronous method

  1. 在事件处理程序中,将调用的方法的名称从 SumPageSizes 更改为 SumPageSizesAsync(如果尚未执行此操作)。In the event handler, change the name of the called method from SumPageSizes to SumPageSizesAsync, if you haven’t already done so.

  2. 由于 SumPageSizesAsync 是异步方法,请更改事件处理程序中的代码以等待结果。Because SumPageSizesAsync is an async method, change the code in the event handler to await the result.

    SumPageSizesAsync 的调用反射对 GetURLContentsAsync 中的 CopyToAsync 的调用。The call to SumPageSizesAsync mirrors the call to CopyToAsync in GetURLContentsAsync. 调用返回的是 Task,而不是 Task(T)The call returns a Task, not a Task(T).

    与之前的过程一样,你可以使用一条语句或两条语句转换调用。As in previous procedures, you can convert the call by using one statement or two statements. 下列代码显示这些更改。The following code shows these changes.

    // One-step async call.
    await SumPageSizesAsync();
    
    // Two-step async call.
    //Task sumTask = SumPageSizesAsync();
    //await sumTask;
    
  3. 要防止意外重新进入操作,请在 startButton_Click 顶部添加下列语句,以禁用“启动”按钮。To prevent accidentally reentering the operation, add the following statement at the top of startButton_Click to disable the Start button.

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

    可以重新启用事件处理程序末尾的按钮。You can reenable the button at the end of the event handler.

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

    有关重新进入的详细信息,请参阅处理异步应用中的重新进入 (C#)For more information about reentrancy, see Handling Reentrancy in Async Apps (C#).

  4. 最后,将 async 修饰符添加到声明,以便事件处理程序可以等待 SumPagSizesAsyncFinally, add the async modifier to the declaration so that the event handler can await SumPagSizesAsync.

    private async void startButton_Click(object sender, RoutedEventArgs e)
    

    通常情况下,事件处理程序的名称不会更改。Typically, the names of event handlers aren’t changed. 返回类型没有更改为 Task,因为事件处理程序必须返回 voidThe return type isn’t changed to Task because event handlers must return void.

    项目从同步处理到异步处理的转换完成。The conversion of the project from synchronous to asynchronous processing is complete.

测试异步解决方案Test the asynchronous solution

  1. 按 F5 键运行程序,然后选择“启动”按钮。Choose the F5 key to run the program, and then choose the Start button.

  2. 此时应显示类似于同步解决方案的输出的输出。Output that resembles the output of the synchronous solution should appear. 但是,请注意下列差异。However, notice the following differences.

    • 处理完成后,所有结果不会同时出现。The results don’t all occur at the same time, after the processing is complete. 例如,两个程序都在 startButton_Click 中包含一行可以清除文本框的代码。For example, both programs contain a line in startButton_Click that clears the text box. 目的在于,在一组结果显示后,第二次选择“启动”按钮时,可以清除运行之间的文本框。The intent is to clear the text box between runs if you choose the Start button for a second time, after one set of results has appeared. 在同步版本中,下载完成且 UI 线程可以自由完成其他工作时,文本框在计数第二次显示之前即被清除。In the synchronous version, the text box is cleared just before the counts appear for the second time, when the downloads are completed and the UI thread is free to do other work. 在异步版本中,选择“启动”按钮后立即清除文本框。In the asynchronous version, the text box clears immediately after you choose the Start button.

    • 最重要的是,UI 线程在下载过程中未被阻止。Most importantly, the UI thread isn’t blocked during the downloads. 在 Web 资源下载、计数和显示期间,可以移动窗口或调整窗口大小。You can move or resize the window while the web resources are being downloaded, counted, and displayed. 如果其中一个网站速度缓慢或没有响应,则可以通过选择“关闭”按钮取消操作(右上角红色字段中的 x)。If one of the websites is slow or not responding, you can cancel the operation by choosing the Close button (the x in the red field in the upper-right corner).

使用 .NET Framework 方法替换 GetURLContentsAsyncReplace method GetURLContentsAsync with a .NET Framework method

  1. .NET Framework 4.5 提供可供你使用的许多异步方法。The .NET Framework 4.5 provides many async methods that you can use. 其中一个是 HttpClient 方法 GetByteArrayAsync(String),它可以执行本演练所需的操作。One of them, the HttpClient method GetByteArrayAsync(String), does just what you need for this walkthrough. 你可以使用它来替代你在早前过程中创建的 GetURLContentsAsync 方法。You can use it instead of the GetURLContentsAsync method that you created in an earlier procedure.

    第一步是在方法 SumPageSizesAsync 中创建 HttpClient 对象。The first step is to create an HttpClient object in method SumPageSizesAsync. 在方法的开头添加下列声明。Add the following declaration at the start of the method.

    // Declare an HttpClient object and increase the buffer size. The
    // default buffer size is 65,536.
    HttpClient client =
        new HttpClient() { MaxResponseContentBufferSize = 1000000 };
    
  2. SumPageSizesAsync, 中,使用对 HttpClient 方法的调用替换对 GetURLContentsAsync 方法的调用。In SumPageSizesAsync, replace the call to your GetURLContentsAsync method with a call to the HttpClient method.

    byte[] urlContents = await client.GetByteArrayAsync(url);
    
  3. 删除或注释禁用你编写的 GetURLContentsAsync 方法。Remove or comment out the GetURLContentsAsync method that you wrote.

  4. 按 F5 键运行程序,然后选择“启动”按钮。Choose the F5 key to run the program, and then choose the Start button.

    此版本的项目的行为应与“测试异步解决方案”过程描述的行为匹配,且你的工作量应该更少。The behavior of this version of the project should match the behavior that the "To test the asynchronous solution" procedure describes but with even less effort from you.

示例代码Example code

下列代码包含使用你编写的异步 GetURLContentsAsync 方法从同步解决方案转换为异步解决方案的完整示例。The following code contains the full example of the conversion from a synchronous to an asynchronous solution by using the asynchronous GetURLContentsAsync method that you wrote. 请注意,它与原始同步解决方案十分类似。Notice that it strongly resembles the original, synchronous solution.

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 +=
                $"\r\n\r\nTotal bytes returned:  {total}\r\n";
        }

        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/library/hh290136.aspx",
                "https://msdn.microsoft.com/library/ee256749.aspx",
                "https://msdn.microsoft.com/library/hh290138.aspx",
                "https://msdn.microsoft.com/library/hh290140.aspx",
                "https://msdn.microsoft.com/library/dd470362.aspx",
                "https://msdn.microsoft.com/library/aa578028.aspx",
                "https://msdn.microsoft.com/library/ms404677.aspx",
                "https://msdn.microsoft.com/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 "https://".
            var displayURL = url.Replace("https://", "");
            resultsTextBox.Text += $"\n{displayURL,-58} {bytes,8}";
        }
    }
}

下列代码包含使用 HttpClient 方法 GetByteArrayAsync 的解决方案的完整示例。The following code contains the full example of the solution that uses the HttpClient method, GetByteArrayAsync.

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 +=
                $"\r\n\r\nTotal bytes returned:  {total}\r\n";
        }

        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/library/hh290136.aspx",
                "https://msdn.microsoft.com/library/ee256749.aspx",
                "https://msdn.microsoft.com/library/hh290138.aspx",
                "https://msdn.microsoft.com/library/hh290140.aspx",
                "https://msdn.microsoft.com/library/dd470362.aspx",
                "https://msdn.microsoft.com/library/aa578028.aspx",
                "https://msdn.microsoft.com/library/ms404677.aspx",
                "https://msdn.microsoft.com/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 "https://".
            var displayURL = url.Replace("https://", "");
            resultsTextBox.Text += $"\n{displayURL,-58} {bytes,8}";
        }
    }
}

请参阅See also