什么是异步编程

什么是异步编程呢?举个简单的例子:

using System.Net.Http;
using System.Threading.Tasks;
using static System.Console; namespace Core
{
class Async
{
static void Main()
{
Start();
End();
} static void Wait()=>WriteLine("waiting...");
static void End()=>WriteLine("end...");
static int Start()
{
WriteLine("start...");
HttpClient client = new HttpClient();
Waiting();
var result = client.GetStringAsync("https://www.visualstudio.com/");
string str = result.Result;
return str.Length;
}
}
}

上面这段代码中,Main方法中的代码是按照自上而下的顺序执行的。网络状况不佳时,Start()方法是比较耗时(注意,这里在Start方法中调用了异步方法GetStringAsync,但该方法在此处是以同步方式执行的,具体原因下文会进行说明),在Start()方法执行完毕之前,整个程序处于阻塞状态。而异步编程可以很好的解决这个问题,一句简单的话来概括异步编程就是,程序无须按照代码顺序自上而下的执行

async/await

C#5.0新增了async和await关键字,使用这两个关键字可以大大简化异步编程

使用 async 关键字可将方法、lambda 表达式匿名方法标记为异步,即,方法中应该包含一个或多个await表达式,但async关键字本身不会创建异步操作。

public async Task Asy()
{
  //do something...
}

这里需要注意一点,若使用async关键字标记的方法中没有使用await关键字(编译器会给出警告但不报错),那么该方法将会以同步方式执行。

定义异步方法的几点要求

定义一个异步方法应满足以下几点:

  • 使用async关键字来修饰方法
  • 在异步方法中使用await关键字(不使用编译器会给出警告但不报错),否则异步方法会以同步方式执行
  • 尽量不使用void作为返回类型,若希望异步方法返回void类型,请使用Task
  • 异步方法名称以Async结尾
  • 异步方法中不能声明使用ref或out关键字修饰的变量

下面定义一个异步方法StartAsync()

static async Task<int> StartAsync()
{
HttpClient client = new HttpClient();
var str = await client.GetStringAsync("https://www.visualstudio.com/");
return str.Length;
}

异步方法的返回类型

  • Task<T>
    如果在调用匿名方法时使用了await关键字,且匿名方法的返回类型是Task<T>,那么我们得到的返回类型是T。若未使用await关键字,则返回类型是Task。未使用await,调用GetStringAsync方法时result是Task类型。

  

从上图我们可以看到调用GetStringAsync方法时未使用await关键字,result是Task类型,我们可以通过GetType()方法来获取result的详细类型信息:

从上图可以看到result的类型全名是System.Threading.Tasks.Task

 

从上图我们可以看到使用await关键字时,result是string类型,而匿名方法GetStringAsync的返回类型是Task<string>

  • Task
    如果在调用匿名方法时使用了await关键字,且匿名方法的返回类型是Task,那么我们得到的返回类型是void。若为使用await关键字,则得到的返回类型是Task。

  • void
    不建议使用void作为异步方法的返回值。
    因为使用Task或Task<TResult>任务作为返回值,其属性携带有关其状态和历史记录的信息,如任务是否完成、异步方法是否导致异常或已取消以及最终结果是什么。而await运算符可访问这些属性。

异步方法执行流程

异步程序执行流程

上图是微软官方提供的讲解异步程序执行流程的图示,并附有解释说明:

The numbers in the diagram correspond to the following steps.

  1. An event handler calls and awaits the AccessTheWebAsync async method.
  2. AccessTheWebAsync creates an HttpClient instance and calls the GetStringAsyncasynchronous method to download the contents of a website as a string.
  3. Something happens in GetStringAsync that suspends its progress. Perhaps it must wait for a website to download or some other blocking activity. To avoid blocking resources, GetStringAsync yields control to its caller, AccessTheWebAsync.
    GetStringAsync returns a Task<TResult> where TResult is a string, and AccessTheWebAsync assigns the task to thegetStringTask variable. The task represents the ongoing process for the call to GetStringAsync, with a commitment to produce an actual string value when the work is complete.
  4. Because getStringTask hasn't been awaited yet, AccessTheWebAsync can continue with other work that doesn't depend on the final result from GetStringAsync. That work is represented by a call to the synchronous method DoIndependentWork.
  5. DoIndependentWork is a synchronous method that does its work and returns to its caller.
  6. AccessTheWebAsync has run out of work that it can do without a result from getStringTask. AccessTheWebAsync next wants to calculate and return the length of the downloaded string, but the method can't calculate that value until the method has the string.
    Therefore, AccessTheWebAsync uses an await operator to suspend its progress and to yield control to the method that called AccessTheWebAsync. AccessTheWebAsync returns a Task<int> to the caller. The task represents a promise to produce an integer result that's the length of the downloaded string.

    Note
    If GetStringAsync (and therefore getStringTask) is complete before AccessTheWebAsync awaits it, control remains inAccessTheWebAsync. The expense of suspending and then returning to AccessTheWebAsync would be wasted if the called asynchronous process (getStringTask) has already completed and AccessTheWebSync doesn't have to wait for the final result.
    Inside the caller (the event handler in this example), the processing pattern continues. The caller might do other work that doesn't depend on the result from AccessTheWebAsync before awaiting that result, or the caller might await immediately. The event handler is waiting for AccessTheWebAsync, and AccessTheWebAsync is waiting for GetStringAsync.

  7. GetStringAsync completes and produces a string result. The string result isn't returned by the call to GetStringAsync in the way that you might expect. (Remember that the method already returned a task in step Instead, the string result is stored in the task that represents the completion of the method, getStringTask. The await operator retrieves the result from getStringTask. The assignment statement assigns the retrieved result to urlContents.
  8. When AccessTheWebAsync has the string result, the method can calculate the length of the string. Then the work ofAccessTheWebAsync is also complete, and the waiting event handler can resume. In the full example at the end of the topic, you can confirm that the event handler retrieves and prints the value of the length result.
    If you are new to asynchronous programming, take a minute to consider the difference between synchronous and asynchronous behavior. A synchronous method returns when its work is complete (step 5), but an async method returns a task value when its work is suspended (steps 3 and 6). When the async method eventually completes its work, the task is marked as completed and the result, if any, is stored in the task.

解释虽是英文,但并没有太难的单词,是可以看懂其意思的。通过上面的说明,我们可以知道:
在遇到awiat关键字之前,程序是按照代码顺序自上而下以同步方式执行的。
在遇到await关键字之后,系统做了以下工作:

  1. 异步方法将被挂起
  2. 将控制权返回给调用者
  3. 使用线程池中的线程(而非额外创建新的线程)来计算await表达式的结果,所以await不会造成程序的阻塞
  4. 完成对await表达式的计算之后,若await表达式后面还有代码则由执行await表达式的线程(不是调用方所在的线程)继续执行这些代码

使用一段代码来进行验证:

static void Main()
{
Task<int> task = StartAsync();
Thread.Sleep();
End();
} static async Task<int> StartAsync()
{
WriteLine("start...");
HttpClient client = new HttpClient();
var result = client.GetStringAsync("https://www.visualstudio.com/");
string str = await result;
return str.Length;
}

执行代码

从上图左侧的调用栈中可以看到,在遇到await关键字之前,异步方法StartAsync自上而下同步执行。注意,这里异步方法GetStringAsync方法是被挂起的,不会造成程序的阻塞,控制权回到调用者StartAsync中,仔细看英文解释中的第3步。
然后在Debug Console中输入System.Threading.Thread.Current查看当前工作线程信息,以及System.Threading.Thread.CurrentThread.IsThreadPoolThread查看当前线程是否在线程池中。

 

从上图我们看到,当前线程Id是1,不在线程池中。继续执行程序:

遇到await关键字后,异步方法StartAsync被挂起,控制权也回到了调用者Main方法中。

从上图我们可以看到异步方法StartAsync中的result变量的Status属性值是WaitingForActivation,Result属性值是Not yet computed

代码继续执行,将Main方法所在线程接挂起5秒,系统使用线程池中的线程计算await表达式的值:

从上图我们可以看到,程序已经成功计算出await表达式的值,变量result的Status属性值变成了RanToCompletion。完成对await表达式的计算之后,程序继续执行后面的代码(return str.Length)。

再看此时的工作线程信息:

我们看到,当前线程Id是5且存在于线程池中。

从这里我们可以得知异步是借助于多线程来实现的

Task

Task类拥有执行异步方法的两个方法:Task.Run(),Task.Run<T>Task.Run以及Task.Run<T>使用线程池中的线程来执行代码,它和使用await关键字的区别是:Task.Run直接使用线程池中的线程,而使用await的异步方法是在遇到await关键字后才使用多线程。

Thread

线程是前面所说的异步(async/await)和任务(Task)的基础。和线程紧密相关的另外一个概念是进程,这里不多赘述。

ThreadPool

线程也是对象,频繁的创建和销毁线程比较影响性能,.NET提供线程池使得我们能够复用线程对象从而避免频繁创建和销毁线程。

结语

自己创建线程比较麻烦但能够更好的控制程序的运行,使用async/await关键字来编码显得较为简洁,但对程序的控制力度会有所降低。

参考文章:

Asynchronous Programming with async and await (C#)
async
await
走进异步编程的世界 - 开始接触 async/await
C#执行异步操作的几种方式比较和总结
thread task parallel plinq async await多线程 任务及异步编程
走进异步编程的世界 - 在 GUI 中执行异步操作

Async/Await - Best Practices in Asynchronous Programming

版权声明

本文为作者原创,版权归作者雪飞鸿所有。
转载必须保留文章的完整性,且在页面明显位置处标明原文链接

如有问题, 请发送邮件和作者联系。

C#异步编程的更多相关文章

  1. C#异步编程(一)

    异步编程简介 前言 本人学习.Net两年有余,是第一次写博客,虽然写的很认真,当毕竟是第一次,肯定会有很多不足之处, 希望大家照顾照顾新人,有错误之处可以指出来,我会虚心接受的. 何谓异步 与同步相对 ...

  2. C#与C++的发展历程第三 - C#5.0异步编程巅峰

    系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...

  3. 关于如何提高Web服务端并发效率的异步编程技术

    最近我研究技术的一个重点是java的多线程开发,在我早期学习java的时候,很多书上把java的多线程开发标榜为简单易用,这个简单易用是以C语言作为参照的,不过我也没有使用过C语言开发过多线程,我只知 ...

  4. 异步编程 In .NET

    概述 在之前写的一篇关于async和await的前世今生的文章之后,大家似乎在async和await提高网站处理能力方面还有一些疑问,博客园本身也做了不少的尝试.今天我们再来回答一下这个问题,同时我们 ...

  5. C#异步编程(二)

    async和await结构 序 前篇博客异步编程系列(一) 已经介绍了何谓异步编程,这篇主要介绍怎么实现异步编程,主要通过C#5.0引入的async/await来实现. BeginInvoke和End ...

  6. [.NET] 利用 async & await 的异步编程

    利用 async & await 的异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/5922573.html  目录 异步编程的简介 异 ...

  7. [.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程

    怎样使用 async & await 一步步将同步代码转换为异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6079707.html  ...

  8. [C#] 走进异步编程的世界 - 开始接触 async/await

    走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...

  9. 深入解析js异步编程利器Generator

    我们在编写Nodejs程序时,经常会用到回调函数,在一个操作执行完成之后对返回的数据进行处理,我简单的理解它为异步编程. 如果操作很多,那么回调的嵌套就会必不可少,那么如果操作非常多,那么回调的嵌套就 ...

随机推荐

  1. ASP.NET Core 之 Identity 入门(一)

    前言 在 ASP.NET Core 中,仍然沿用了 ASP.NET里面的 Identity 组件库,负责对用户的身份进行认证,总体来说的话,没有MVC 5 里面那么复杂,因为在MVC 5里面引入了OW ...

  2. 制作类似ThinkPHP框架中的PATHINFO模式功能

    一.PATHINFO功能简述 搞PHP的都知道ThinkPHP是一个免费开源的轻量级PHP框架,虽说轻量但它的功能却很强大.这也是我接触学习的第一个框架.TP框架中的URL默认模式即是PathInfo ...

  3. IE10、IE11 User-Agent 导致的 ASP.Net 网站无法写入Cookie 问题

    你是否遇到过当使用一个涉及到Cookie操作的网站或者管理系统时,IE 6.7.8.9下都跑的好好的,唯独到了IE10.11这些高版本浏览器就不行了?好吧,这个问题码农连续2天内遇到了2次.那么,我们 ...

  4. ASP.NET MVC5+EF6+EasyUI 后台管理系统(76)-微信公众平台开发-网页授权

    前言 网页授权是:应用或者网站请求你用你的微信帐号登录,同意之后第三方应用可以获取你的个人信息 网上说了一大堆参数,实际很难理解和猜透,我们以实际的代码来演示比较通俗易懂 配置 实现之前我们必须配置用 ...

  5. 15个关于Chrome的开发必备小技巧[译]

    谷歌Chrome,是当前最流行且被众多web开发人员使用的浏览器.最快六周就更新发布一次以及伴随着它不断强大的开发组件,使得Chrome成为你必备的开发工具.例如,在线编辑CSS,console以及d ...

  6. 从零开始编写自己的C#框架(28)——建模、架构与框架

    文章写到这里,我一直在犹豫是继续写针对中小型框架的设计还是写些框架设计上的进阶方面的内容?对于中小型系统来说,只要将前面的内容进行一下细化,写上二三十章具体开发上的细节,来说明这个通用框架怎么开发的就 ...

  7. JQuery easyUI DataGrid 创建复杂列表头(译)

    » Create column groups in DataGrid The easyui DataGrid has ability to group columns, as the followin ...

  8. Golang 编写的图片压缩程序,质量、尺寸压缩,批量、单张压缩

    目录: 前序 效果图 简介 全部代码 前序: 接触 golang 不久,一直是边学边做,边总结,深深感到这门语言的魅力,等下要跟大家分享是最近项目 服务端 用到的图片压缩程序,我单独分离了出来,做成了 ...

  9. AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache

    这篇我们就要介绍AFAutoPurgingImageCache这个类了.这个类给了我们临时管理图片内存的能力. 前言 假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来 ...

  10. Android菜单项内容大全

    一.介绍: 菜单是许多应用中常见的用户界面组件. Android3.0版本以前,Android设备会提供一个专用"菜单"按钮呈现常用的一些用户操作, Android3.0版本以后, ...