PDC 2010:C#与Visual Basic的未来(中)
前几天在PDC 2010会议上Anders Hejlsberg发表了一场名为“The Future of C# and Visual Basic”的演说,谈论了未来C#和VB中最为重要的两个特性:“异步(Async)”及“编译器即服务?ompiler as a Service)”。我现在对这场演讲进行总结,但不会像上次《编程语言的发展趋势及未来方向》那样逐句翻译,而是以Anders的角度使用一种简捷合适的方式表述其完整内容。上一篇Anders讲述了async和await的使用方式,而这篇则是对这两个关键字的实现及效果作更进一步的解释。

异步方法的目标,是为了让代码与同步方法保持一致。微软要让代码充斥着回调函数,混乱不堪,它们完全不是逻辑上你想做的事情。可能您的代码中包含着一个核心模型,你也已经实现了,只是您现在想把它的执行过程变得异步化。您自己就可以享受到这一点。
与我们之前做的一些扩展一样,工作分为语言和框架两部分。语言的异步功能基于框架中的Task
而在语言方面,我们添加了两个新的关键字。一个是async关键字,用于把方法标记为异步。还有一个是await方法,用于等待异步工作完成,或者说是把控制权交换给调用方继续执行其他工作。这两个功能在C#和VB种均有体现。

那么什么是Task
Task
此外,它还提供了一个可组合的回调模型,您可以对一个任务指定说,在它结束之后执行另外一段代码,然后还可以对这个新的任务继续进行设定。这便构造出一个完整的逻辑流,框架会自行帮你完成这些工作。事实上await操作符便会自动把您的逻辑改写成这样的代码,它将您从Lambda表达式及回调函数中的逻辑里解放了出来,一切都交给编译器去做了。您可能会有些疑惑,不过其实这些都是编译器所擅长的事情。
由于我们统一了异步模型,我们就可以在此之上构建组合工具。例如WhenAll,它接受一系列的Task对象,并在全部结束之后返回所有结果。还有WhenAny,则等待第一个完成的任务,返回其结果。我们还有Delay,可以等待一段时间,但不占用任何资源。

沿着这个过程走一遍可能就会清晰一些。这里有个例子,一个异步方法调用另一个异步方法。我们假设这是在UI线程上执行的,消息会一个一个发送至UI线程上。

好,有人调用了DoWorkAsync,于是出现了一些任务。

DoWorkAsync的第一件事,是调用了ProcessFeedAsync。

ProcessFeedAsync方法是一个异步方法,所以它做的第一件事是构造一个表示任务的Task对象。

然后它调用了DownloadFeedAsync,这会创建另一个Task对象。然后,我们遇上了await操作符,这意味着ProcessFeedAsync后面的部分,将作为DownloadFeedAsync完成后的回调函数/continuation里的工作。

于是任务返回至DoWorkAsync,我们得到了t1这个对象。

同样的过程会再次出现,是为t2。

然后便调用了Task.WhenAll,这会创建一个新任务,表示前两个任务全部完成。于是这里的await操作符表示接下去的代码会在前两个任务完成后再继续下去。此时控制权便还给了DoWorkAsync的调用者,不会对线程造成负担。

在未来某一时刻t1和t2会执行完,我们假设t2先结束。此时它会说:我完成了,执行回调函数/continuation吧。

于是它会和发起线程的SynchronizationContext交互,给UI线程发一个消息,让后续任务在UI线程上继续执行──您的代码不用关注这些。现在代码运行至SaveDocAsync上了,这是另外一个异步任务。await让代码在这里返回,线程又可以执行目前还未结束的任务了。

于是SaveDocAsync任务完成了,UI线程又获得了一个消息执行后续工作。

此时任务便到达了ProcessFeedAsync的末尾,于是t2任务结束了。

继续等待,上面的过程会再次出现,最终t1也结束了。

当t1和t2完成以后,最后DoWorkAsync任务也终于结束了。可以看到,我们逻辑流程,无论是循环还是异常捕获都是同步的,但是其中的执行过程完全是异步的。

但是这又是如何实现的?我不会在这里说太细,这又是个完整的话题了。这里有一个例子,是一个异步方法,它会调用并await另一个异步方法。

而编译器则最终则生成类似于这样的代码。我只会提几点,首先,这是个状态机,编译器构造的其实就是个状态机,例如迭代器就是个状态机,事实上这里编译器的工作和yield之余迭代器的重写本质上没有太大区别。
其次就是关于任务的执行和等待,假如在等待时任务已经完成了,那么其实您是在同步地执行后续代码。我们没有必要交还控制,反正已经完成了,我们不妨就直接进行下去了。await有自己的模式,会决定这一任务是同步还是异步地执行。对于同步执行的任务,一切就继续执行下去了,直到某个需要异步执行的地方,便把控制权交还给调用方。

那么我们再来看一下异步之于Web服务的意义。这里有个ASP.NET页面,它会向数据库里获取许多RSS地址,然后下载到本地并解析:
private void ProcessData() { // ... var urls = new List(); using (var conn = new SqlConnection(connectionString)) { conn.Open(); var cmd = new SqlCommand("GetUserFeeds", conn); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.AddWithValue("@UserID", user); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) urls.Add(reader["FeedURL"].ToString()); } } var feeds = (from url in urls select CreateWebClient().DownloadString(url)).ToArray(); // ... }
这里用到了DownloadString这个同步下载数据的方法。执行下来大约要花费1秒多的时间。这里我不再演示令人痛苦的异步写法了,你必须在Page_Load和Page_PreRender各写一些逻辑,注册一些异步工作,或者就要启用一些后台线程,但这又会影响后台的线程池,对系统的表现会带来影响。
现在我来演示一些简单的异步化工作:
private async void ProcessData() { // ... var urls = new List(); using (var conn = new SqlConnection(connectionString)) { conn.Open(); var cmd = new SqlCommand("GetUserFeeds", conn); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.AddWithValue("@UserID", user); using (var reader = await cmd.ExecuteReaderAsync()) { while (reader.Read()) urls.Add(reader["FeedURL"].ToString()); } } var feeds = await TaskEx.WhenAll( from url in urls select CreateWebClient().DownloadStringTaskAsync(url)); // ... }
我们将DownloadString修改为DownloadStringTaskAsync,这样LINQ返回的就是一系列表示下载任务的Task对象,然后使用await及WhenAll等待它们全部完成。数据库查询也可以如此。这就是所有我们要做的事情。如今页面的执行效率有了很明显的提高。使用这个做法,我们可以很轻松地提高Web系统的伸缩能力。如今我们需要调用很多互相独立的服务的情况越来越多了,异步方法对此有很大帮助。

如今的异步场景有许多种,例如在后台执行一个计算任务,这是基于CPU的异步,还有基于网络或I/O的异步任务。这些都能用Task来表示出来,因为Task表示的就是未来会完成的异步任务。此外,有了async和.NET框架,我们则出现了另外一种任务,既基于某些任务组合而成的异步任务。这也就是async方法所体现出的异步任务,它可以让你使用传统的语句来构造异步执行过程。
例如有这么一个场景:获取链接,根据链接下载Youtube视频,根据下载到的视频创建mashup并组合起来。在执行这些工作的时候,我们也希望UI可以响应用户操作。

而要完成这些工作,代码可能只需要这么简单,完全就像同步代码一样。而这里也体现了多种异步任务:ScrapeYoutubeAsync是网络密集型任务,然后同时下载两个视频并等待它们结束。然后MashupVideosAsync是CPU密集型任务,然后最后则是I/O密集型的的SaveAsync操作。对于异常处理来说,我们可以简捷地使用一个try...catch,就像传统编程那样。

总结一下,一个异步方法可以让代码和同步实现一样简单,并统一了计算、网络及I/O的异步化。这可以用来创建高度伸缩的服务器程序,自然还有响应度高的UI程序。
在演讲的末尾,我会给出Visual Studio Async CTP的下载链接,我很乐于得到大家的反馈。
建议继续学习:
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:老赵 来源: 老赵点滴
- 标签: PDC VisualBasic
- 发布时间:2011-02-14 21:22:37
-
[72] Java开发岗位面试题归类汇总
-
[56] 如何拿下简短的域名
-
[55] android 开发入门
-
[55] 【社会化设计】自我(self)部分――欢迎区
-
[55] IOS安全–浅谈关于IOS加固的几种方法
-
[53] find命令的一点注意事项
-
[53] Oracle MTS模式下 进程地址与会话信
-
[51] 图书馆的世界纪录
-
[49] Go Reflect 性能
-
[47] 关于恐惧的自白