《C#并发编程经典实例》学习笔记—异步编程关键字 Async和Await

C# 5.0 推出async和await,最早是.NET Framework 4.5引入,可以在Visual
Studio
2012使用。在此之前的异步编程实现难度较高,async使异步编程的实现变得简便。

本文内容

  • 概述
  • 编写异步方法
  • 异步程序中的控制流
  • API 异步方法
  • 线程
  • 异步和等待
  • 返回类型和参数
  • 参考资料

各平台对async的支持情况

下载 Demo

平台 async
.NET 4.5及以上
.NET 4.0 NuGet
Mono iOS/Droid
Windows Store
Windows Phone Apps 8.1
Windows Phone SL 8.0
Windows Phone SL 7.1 NuGet
Silverlight 5 NuGet

下载 Demo TPL 与 APM 和 EAP 结合(APM 和 EAP 这两个标准异步方式已经不能适应多核时代,但之前用这两种方式写的代码怎么办?——把它们改造一下,跟 TPL 结合)

在不支持的平台,安装NuGet包 Microsoft.Bcl.Async

概述


异步对可能起阻止作用的活动(例如,应用程序访问 Web 时)至关重要。 对 Web
资源的访问有时很慢或会延迟。
如果此类活动在同步过程中受阻,则整个应用程序必须等待。在异步过程中,应用程序可继续执行不依赖
Web 资源的其他工作,直至潜在阻止任务完成。

下表是利用异步编程能提高响应能力的典型场景。从 .NET Framework 4.5 和
Windows 运行时中列出的 API 包含支持异步编程的方法。

应用程序区域

包含异步方法的受支持的 API

Web 访问

HttpClient ,SyndicationClient

使用文件

StorageFile、StreamWriter、StreamReader、XmlReader

使用图像

MediaCapture、BitmapEncoder、BitmapDecoder

WCF 编程

同步和异步操作

由于所有与用户界面相关的活动通常共享一个线程,因此,异步对访问 UI
线程的应用程序来说尤为重要。如果任何进程在同步应用程序中受阻,则所有进程都将受阻。
你的应用程序停止响应,因此,你可能在其等待过程中认为它已经失败。

使用异步方法时,应用程序将继续响应 UI。
例如,你可以调整窗口的大小或最小化窗口;如果你不希望等待应用程序结束,则可以将其关闭。

可以使用三种方式来实现 TAP:即手动使用 C#
编译器,或将编译器和手动方法结合使用。使用 TAP 模式来实现计算密集型和
I/O 密集型异步操作。

  • 使用编译器。在 Visual Studio 2012 和 .NET Framework 4.5
    中,任何具有 async 关键字的方法都被看作是一种异步方法,并且 C#
    会执行必要的转换,以通过 TAP 来异步实现该方法。 异步方法应返回
    System.Threading.Tasks.Task 或
    System.Threading.Tasks.Task<TResult> 对象。
  • 手动生成 TAP 方法。也可以手动实现 TAP
    模式,以更好地控制实现。编译器依赖从 System.Threading.Tasks
    命名空间公开的公共外围应用和 System.Runtime.CompilerServices
    命名空间中支持的类型。 如要自己实现 TAP,你需要创建一个
    TaskCompletionSource<TResult>
    对象、执行异步操作,并在操作完成时,调用
    SetResult、SetException、SetCanceled
    方法,或调用这些方法之一的Try版本。 手动实现 TAP
    方法时,需在所表示的异步操作完成时完成生成的任务。 例如:
  • 混合方法。你可能发现手动实现 TAP
    模式、但将实现核心逻辑委托给编译器的这种方法很有用。
    例如,当你想要验证编译器生成的异步方法之外的实参时,可能需要使用这种混合方法,以便异常可以转义到该方法的直接调用方而不是通过
    System.Threading.Tasks.Task 对象被公开:

本文主要说明“使用编译器”方法。

使用 async 修饰符可将方法、lambda 表达式或匿名方法指定为异步。

编写异步方法


C# 中 asyncawait
关键字是异步编程的核心。通过这两个关键字就可以轻松创建异步方法,几乎与创建同步方法一样。如下所示的
WPF 程序,布局文件上有个按钮和文本框:

private async void StartButton_Click(object sender, RoutedEventArgs e)

{

    // Call and await separately.

    //Task<int> getLengthTask = AccessTheWebAsync();

    //// You can do independent work here.

    //int contentLength = await getLengthTask;

 

    int contentLength = await AccessTheWebAsync();

 

    resultsTextBox.Text +=

        String.Format("rnLength of the downloaded string: {0}.rn", contentLength);

}

 

 

// Three things to note in the signature:

//  - The method has an async modifier. 

//  - The return type is Task or Task<T>. (See "Return Types" section.)

//    Here, it is Task<int> because the return statement returns an integer.

//  - The method name ends in "Async."

async Task<int> AccessTheWebAsync()

{ 

    // You need to add a reference to System.Net.Http to declare client.

    HttpClient client = new HttpClient();

 

    // GetStringAsync returns a Task<string>. That means that when you await the

    // task you'll get a string (urlContents).

    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

 

    // You can do work here that doesn't rely on the string from GetStringAsync.

    DoIndependentWork();

 

    // The await operator suspends AccessTheWebAsync.

    //  - AccessTheWebAsync can't continue until getStringTask is complete.

    //  - Meanwhile, control returns to the caller of AccessTheWebAsync.

    //  - Control resumes here when getStringTask is complete. 

    //  - The await operator then retrieves the string result from getStringTask.

    string urlContents = await getStringTask;

 

    // The return statement specifies an integer result.

    // Any methods that are awaiting AccessTheWebAsync retrieve the length value.

    return urlContents.Length;

}

 

 

void DoIndependentWork()

{

    resultsTextBox.Text += "Working . . . . . . .rn";

}

执行结果:

Working . . . . . . .

 

Length of the downloaded string: 41609.

说明:

1,当程序访问网络时,无论你如何拖拽、最大化最小化、如何点击,UI
都不会失去响应;

2,“async Task<int>
AccessTheWebAsync()”方法签名,有三点需要注意:1)有 async
修饰符;2)返回类型是 TaskTask<int>。该方法是
Task<int>,因为它返回的是链接内容的大小;3)方法名以 Async
结尾;

3,“string urlContents = await
getStringTask;”语句,有四点需要注意:1)AccessTheWebAsync 方法直到
getStringTask 完成才能继续;2)同时,控制流返回到
AccessTheWebAsync 的调用者;3)getStringTask
完成后,控制流才会恢复;4)之后,await 操作符从 getStringTask
检索结果。

下面总结让一个示例成为异步方法的特征:

  • 方法签名包含一个 async 修饰符。
  • 按照约定,异步方法的名称以“Async”后缀结尾。
  • 返回类型为下列类型之一:
    • 如果你的方法有 TResult 类型的返回语句,则为
      Task<TResult>。
    • 如果你的方法没有返回语句,则为 Task。
    • 如果你编写的是异步事件处理程序,则为 Void(Visual Basic 中为
      Sub)。
  • 方法通常包含至少一个 await
    表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。同时,将方法挂起,并且控件返回到方法的调用方。

在异步方法中,可使用提供的关键字和类型来指示需要完成的操作,且编译器会完成其余操作,其中包括持续跟踪控件以挂起方法返回等待点时发生的情况。
一些常规流程(例如,循环和异常处理)在传统异步代码中处理起来可能很困难。
在异步方法中,元素的编写频率与同步解决方案相同且此问题得到解决。

async 对方法做了什么处理

从使用async修饰符修饰的方法的IL代码可以得出一个结论:

  • 在Debug下,针对async方法,生成的是一个class状态机
  • 在Release下,针对async方法,生成的是一个struct状态机

举例:
C#代码如下

using System.Threading.Tasks;

namespace ConsoleApp3
{
    public class Test
    {
        public async Task TestAsync()
        {
            await GetAsync();
        }

        public async Task GetAsync()
        {
            await Task.Delay(1);
        }
    }
}

以TestAsync方法为准

Release下 初始化状态机V_0
,类型是值类型Struct(valuetype),类型名称为<TestAsync>d__0

    .locals init (
      [0] valuetype ConsoleApp3.Test/'<TestAsync>d__0' V_0,
      [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder V_1
    )

<TestAsync>d__0 继承值类型[mscorlib]System.ValueType

.class nested private sealed auto ansi beforefieldinit 
    '<TestAsync>d__0'
      extends [mscorlib]System.ValueType
      implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine

Debug 下 初始化状态机V_0 ,类型是引用类型Class(class)
,类型名称为<TestAsync>d__0

    .locals init (
      [0] class ConsoleApp3.Test/'<TestAsync>d__0' V_0,
      [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder V_1
    )

<TestAsync>d__0 继承引用类型[mscorlib]System.Object

  .class nested private sealed auto ansi beforefieldinit 
    '<TestAsync>d__0'
      extends [mscorlib]System.Object
      implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine

异步程序中的控制流


异步编程中最需弄清的是控制流是如何从方法移动到方法。

private async void StartButton_Click(object sender, RoutedEventArgs e)

       {

           // Call and await separately.

           //Task<int> getLengthTask = AccessTheWebAsync();

           //// You can do independent work here.

           //int contentLength = await getLengthTask;

           resultsTextBox.Text += "1:  Entering startButton_Click.rn" +

               "           Calling AccessTheWebAsync.rn";

 

           int contentLength = await AccessTheWebAsync();

 

           resultsTextBox.Text +=

               String.Format("rn6:   Length of the downloaded string: {0}.rn", contentLength);

       }

 

       async Task<int> AccessTheWebAsync()

       {

           resultsTextBox.Text += "rn2:  Entering AccessTheWebAsync.";

 

           HttpClient client = new HttpClient();

 

           resultsTextBox.Text += "rn        Calling HttpClient.GetStringAsync.rn";

 

           Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

 

           DoIndependentWork();

 

           resultsTextBox.Text += "rn4:  Back in startButton_Click.rn" +

               "       Task getStringTask is started.rn";

           string urlContents = await getStringTask;

 

           resultsTextBox.Text += "rn5:  Back in AccessTheWebAsync." +

               "rn       Task getStringTask is complete." +

               "rn       Processing the return statement." +

               "rn       Exiting from AccessTheWebAsync.rn";

 

           return urlContents.Length;

       }

 

 

       void DoIndependentWork()

       {

           resultsTextBox.Text += "rn3:  Entering DoIndependentWork.rn";

 

           resultsTextBox.Text += "rn        Working . . . . . . .rn";

       }

运行结果:

1:  Entering startButton_Click.

           Calling AccessTheWebAsync.

 

2:  Entering AccessTheWebAsync.

        Calling HttpClient.GetStringAsync.

 

3:  Entering DoIndependentWork.

 

        Working . . . . . . .

 

4:  Back in startButton_Click.

       Task getStringTask is started.

 

5:  Back in AccessTheWebAsync.

       Task getStringTask is complete.

       Processing the return statement.

       Exiting from AccessTheWebAsync.

 

6:   Length of the downloaded string: 41609.

再稍微复杂点:

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.rn" +

               "           Calling AccessTheWebAsync.rn";

 

           Task<int> getLengthTask = AccessTheWebAsync();

 

           resultsTextBox.Text += "rnFOUR:  Back in startButton_Click.rn" +

               "           Task getLengthTask is started.rn" +

               "           About to await getLengthTask -- no caller to return to.rn";

 

           int contentLength = await getLengthTask;

 

           resultsTextBox.Text += "rnSIX:   Back in startButton_Click.rn" +

               "           Task getLengthTask is finished.rn" +

               "           Result from AccessTheWebAsync is stored in contentLength.rn" +

               "           About to display contentLength and exit.rn";

 

           resultsTextBox.Text +=

               String.Format("rnLength of the downloaded string: {0}.rn", contentLength);

       }

 

       async Task<int> AccessTheWebAsync()

       {

           resultsTextBox.Text += "rnTWO:   Entering AccessTheWebAsync.";

 

           // Declare an HttpClient object and increase the buffer size. The default

           // buffer size is 65,536.

           HttpClient client =

               new HttpClient() { MaxResponseContentBufferSize = 1000000 };

 

           resultsTextBox.Text += "rn           Calling HttpClient.GetStringAsync.rn";

 

           // GetStringAsync returns a Task<string>. 

           Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

 

           resultsTextBox.Text += "rnTHREE: Back in AccessTheWebAsync.rn" +

               "           Task getStringTask is started.";

 

           // AccessTheWebAsync can continue to work until getStringTask is awaited.

 

           resultsTextBox.Text +=

               "rn           About to await getStringTask and return a Task<int> to startButton_Click.rn";

 

           // Retrieve the website contents when task is complete.

           string urlContents = await getStringTask;

 

           resultsTextBox.Text += "rnFIVE:  Back in AccessTheWebAsync." +

               "rn           Task getStringTask is complete." +

               "rn           Processing the return statement." +

               "rn           Exiting from AccessTheWebAsync.rn";

 

           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 and 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: 41635.

发表评论

电子邮件地址不会被公开。 必填项已用*标注