下一代C#里的async和await(转)
http://www.cnblogs.com/GrayZhang/archive/2011/09/07/csharp-vnext.html
C#发展至今,已经从最初的1.0到了4.0版本,不如来回顾一下各个版本都带来了什么:
- 1.0版本 - 基本C#语法。
- 2.0版本 - 泛型的支持,CLR进行了升级,从根本上支持了运行时泛型。
- 3.0版本 - LINQ,添加了
from
/join
等类SQL关键字,添加了扩展函数,添加了编译期动态类型var关键字。 - 4.0版本 - dynamic关键字,CLR进行升级,加入DLR,开始对动态进行友好的支持。同时加入动态参数、参数默认值、泛型协变等特性。
可以看到,C#从诞生至今,经历2次CLR的升级,以及1次语法层面的扩展,其作为一个语言已经非常便利、强大。但是随着时代的发展,C#依旧在不 断前进,而下一代C# vNext又即将诞生。每一代的C#都会在小的语法调整之外,带来一个震撼性的特性。从2.0的泛型、3.0的查询到4.0的动态,每一个版本的C#都有 着一个主导的思想,而其他细节的改进和调整则是围绕着这个最基本的思想给予支持。
在这样一路明确的有且只有一个主导思想的升级路线上,下一代的C# vNext的核心思想又是什么呢?纵观当下的软件工程界,最热门的话题莫过于并行计算,为此C#早在4.0版本中就已经引入了Parallel Linq扩展,简化并行的开发。但是这远远不够,即便Parallel Linq已经提供了极大的便利,但其执行-回调模型依旧打破了编码人员以往对代码就是一行一行顺序执行的习惯思维。因此,C# vNext的主导思想是在这之上再给予更多的进化,即C# vNext将着眼于:
异步
C# vNext为了将异步变得更为简单,引入了2个关键字,async和await,下面简单介绍下这2个关键字给我们的编程带来怎么样的改变。
以一个标准的逻辑为例:下载一个远程URI,并将内容输出在界面上,假设我们已经有了显示内容的方法:
void Display(string text) {
// 不管是怎么实现的
}
如果用标准的同步式写法,这代码相当之容易:
void ShowUriContent(string uri) {
using (WebClient client = new WebClient()) {
string text = client.DownloadString(uri);
Display(text);
}
}
当然这不是我们讨论的重点,同步方式会造成线程的阻塞,必须选择WebClient
下载完成才可以继续运行,如果这个过程在UI线程上执行,则会造成UI无响应的情况。同时网络是非常不可预测的外部条件,很可能因为网络状况不好导致程序长时间没有响应,显然不是我们希望得到的结果。
所以我们又有了异步的方案,.NET中最早的异常编程模式是Begin/End模式,不过WebClient
作为WebRequest
的高层封装,已经把这个模式给封装了:
void DownloadUri(string uri) {
using (WebClient client = new WebClient()) {
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(ShowContent);
client.DownloadStringAsync(uri);
}
}
void ShowContent(object sender, DownloadStringCompletedEventArgs e) {
Display(e.Result);
}
看看,好好的事情一变成异步,就变得麻烦无比。一个很明确逻辑的方法活生生拆成2个来处理,虽然可以用Lambda或者delegate来使代码上 进行简化,但依旧无可避免一段逻辑被拆成两段的痛苦。当更多的异步操作交叉在一起的时候,无论是代码的组织还是逻辑的梳理都会变得更加麻烦。
正因为如此,C# vNext引入了关键了,从语法上对此进行了改进,当使用async和await时,我们的代码会变成这样:
void async ShowUriContent(string uri) {
using (WebClient client = new WebClient()) {
string text = await client.DownloadStringTaskAsync(uri);
Display(text);
}
}
悄悄地告诉你,我写上面这段代码的时候,是直接把同步方案的代码复制过来再稍微发了几个字符的……由此可见,在语言级别给予支持后,代码的编写将会是如何地顺畅和简便。这段代码看上去就是一段典型的同步逻辑,创建-下载-显示按部就班,唯一不同地就是在方法声明中加入了async关键字,在DownloadStringTaskAsync
方法的调用时加入了await关键字。就这么神奇地,运行时变成了异步。ShowUriContent
方法会在调用DownloadStringTaskAsync
后退出,而下载过程会异步进行,当下载完成后,再进入Display
方法的执行,期间不会阻塞线程,不会造成UI无响应的情况。
虽然高手们总是说不要关心语言,不要在意语言,真正重要的是思想。但是看着这样的代码,真的还能认为语言的优秀与否对生产效率没有影响吗?
至于如何实现这个效果,本篇并不想做太多的说明,因为本文的目的仅仅是向大家介绍一下下一代C#的一个特性。实现机制方面,相信大家都想得到,编译器会将方法体在await关 键字前后打断,编译为Begin/End模式的异步模型。这并不是什么难事,但是能想到并付诸于实施却并不容易。至少JAVA7虽然强化了异步编程,但却 没有让语言达到这样的程度。请不要不屑于语法糖,正如高手们所说,无论什么语言都不见得能改变设计的代价,那么实现过程的效率,就决定了项目本身的生产效 率。
说回来,这个思想和老赵的[Jscex](http://blog.zhaojie.me/tag/Jscex/ 老赵点滴 - 追求编程之美)非常类似,都是试图通过一步编译,将异步的编程模型统一为同步模型,简化开发复杂度,提升生产效率。时至如今,还想说中国的程序员搞不出创 造性的东西吗?
PS1:怎么去体验下C# vNext。
- 装备好Visual Studio 2010 + SP1,无论什么版本。
- 把Visual Studio Async CTP下载下来,并安装。
- 建个项目,现在你已经可以使用async和await关键字了,而诸如
WebClient
下的DownloadStringTaskAsync
方法,则是在%MyDocument%\Microsoft Visual Studio Async CTP\Samples\AsyncCtpLibrary.dll
下定义的扩展方法。
关于具体的实践和原理,可以看一看[C# 5.0 vNext - New Asynchronous Pattern](http://www.codeproject.com/KB/cs/async.aspx)(http://www.codeproject.com/KB/cs/async.aspx)等文。
PS:最后说说为什么我要写这一篇。原本我是不想写关于.NET的内容的,毕竟已经有不短的时间游走在.NET社区的边缘,没有深入地研究,并不适 合来发表一些自己的论点。但是看到近段时间博客园首页上依旧遍历着诸如“C#最新特性”这样的文章时,真的觉得很不舒服。C# 4.0已经出来多久了,C# 5.0都离我们只有多少距离了,我相信关于Async CTP的内容有不少喜欢逛国外博客的园友是接触过的,但是却没有一个人愿意将这些最前沿的消息分享过来……
当然我也并不认为这个社区的每一员都有义务将自己所知道的内容分享出来,但是现在国内的.NET社区确实存在着这样的现状:大家分享的多数是已经被 嚼得稀巴烂的骨头,什么“C#的可变参数”,什么“自己写一个AJAX实现”,这些或许对你个人的学习有着历史性的意义,但是这样已经存在、流传已久,几 乎家喻户晓的概念,在博客园首页这样的位置传播,真的有其意义所在吗?而这些最前沿的变化、那些更加深入的探究,或者是没人接触,或者就是接触了但不愿意 分享。这并不是我所希望的社区状态,如今的互联网,无论是SNS还是微博,大家都在注重着消息的传播这一环节,为什么在.NET社区里,却是死气沉沉的状 态,把旧菜炒了又炒,满眼都见不到一丝亮点?
C# 5.0功能之Async一瞥
距离微软发布Async CTP已经有个把月了吧,周围大家都在热议着Async。如果你对Async已经非常熟悉,那么,请直接略过……如果你跟我一样,只会一点点异步编程,但又觉得以前的异步编程比较麻烦,那么,让我们一起来探索一下下一代的C#会给我们带来什么。(Async CTP同样对VB有支持的。)
本文的例子基于Async CTP SP1 Refresh完成。由于 Async还处于CTP阶段,很多东西还在讨论,因此,也许待到C# 5.0发布的时候,细节还会变动。但是,大体的思路,概念应该是不会有什么变化了。
进入正题:
首先,要试用Async功能,我们需要安装Visual Studio 2010 SP1和Microsoft Visual Studio Async CTP (SP1 Refresh)。
我们首先设定一个简单的任务,分别来看一下,同步编程,利用回调进步异步编程和Async编程的方法,然后来通过他们来分析一下,Async到底是什么,它给我们带来了什么。
任务:
建立一个Windows Form应用程序,当点击按钮时,先显示一行字,例如,开始计算什么的,用以表示状态,然后计算从1到int.Max/2的累加,并把结果显示出来。
同步我们会这么做:
首先,写一个函数来实现基本算法:
#region Do things |
然后,添加一个按钮的Click事件处理程序:
private void btnSync_Click(object sender, EventArgs e) |
代码第一行改写Label的字样;第二行调用算法获得结果;第三行把结果输出。看似挺不算的。运行一下,就会发现有两个问题:
- 这个算法需要四五秒钟左右的实现时间,并且在这几秒钟的时间里,界面是锁死的,也就是说应用程序就像死了一样,它不接受任何用户操作。(也许我的电脑比较差,呵呵,所以,如果你没有遇到这种情况,请加大输入参数的值,让它算一会儿。)
- 我们没有看到Start to do something这一行字。
OK,出现这个现象也是可以理解的,因为我们把大量的运算添加到了UI线程里面了。所以,解决方法就是把它放到外面。我试了一下不用Async,实现的代码如下:
private void btnCallback_Click(object sender, EventArgs e) |
如果你觉得这段代码比较晕,那就跳过这一节吧。可能我代码写得不好,大家将就看我简单解释一下,我首先给DoSomething写了一个代理,然后,调用了代理的BeginInvoke方法,把算法放到了其它的Thread中去调用了。这个代理执行完了以后,因为它不会直接返回一个long型的值,而是会去执行一个AsyncCallBack,所以,就在这个Callback里,去调用这个代理的EndInvoke()。
好吧,且不论代码质量,这个就是有Async之前的一种实现异步的方法。
从这个代码里,我们完全看不到原来代码的影子,我也没有办法像解释同步代码一样解释:第一、第二、第三……有了Async之后呢?呵呵,代码说明一切:
public Task<long> DoSomethingAsync(int n) |
三件事:
第一:添加文件引用:AsyncCtpLibrary.dll。相信Async正式发布之后,这个会出现在.NET应用程序集里。
第二:把DoSomething封装成一个Task。
第三:添加一些关键字,例如async,例如await。
我们来仔细看一下代码:
首先,我把要异步执行的代码的返回值写成Task<T>。这个返回值其实有三个选项:void,Task和Task<T>,具体怎么用,大家查MSDN上C#4.0中的Task类吧。
然后,我调用了TaskEx中的Run<long>方法,传递给它一个返回值为long的方法——就是我们的任务的算法啦。
如果你有兴趣研究,可以看一下Run<T>其实调用了Task.Factory.StartNew<T>,而这个Start<New>则是先建了一个Task,然后调用了它的Start方法……
好,把算法封成任务部分完成。
第二部分代码比较容易解释了:
第一行改写Label的字样;第二行调用算法获得结果;第三行把结果输出。<--本行复制/粘贴自前文:-)
呵呵,让我们看看细一点,比较一下Sync和Async的代码:
Sync |
Async |
private void btnSync_Click(object sender, EventArgs e) |
private async void btnAsync_Click(object sender, EventArgs e) |
首先,我们在方法名上加上async修饰,声明这是一个有异步调用的方法;
然后,我们在返回Task<T>的函数调用(DoSomethingAsync)之前添加一个await关键字。来猜猜看x是什么类型的?答案是long型。有了await之后,即使在设计时,编译器会自动把Task<T>的类型,转换成T类型。
代码到这里结束了,但是,新的Async功能给我们带来了什么?是异步编程的能力吗?我们用Callback同样可以实现异步,而IAsyncCallback接口应该在.NET 1.1中已经实现了;多线程的命名空间也早就存在;Task在C# 4.0中被引入……
我想,Async给程序员带来的是一种代码逻辑为中心并且实现多线程编程的方式。通过最后的比较,我们看到,Async的代码与Sync的代码相差无几,程序不再需要花大量精力去考虑回调、同步等等的问题……这与C#一直在努力的方向是一致的,程序员更多的来描述是什么而不是怎么做。
最后,附上应用程序下载和源代码,还有运行界面截图……(好吧,我不是美工,请原谅 :-) )
点击Async,看到运行提示:
显示执行结果:
最新最官方的Async资料在这儿:^v^
Little knowledge is dangerous.