async/task/await三组合是.NET Framework 4.5带给.NET开发者的大礼,合理地使用它,可以提高应用程序的吞吐能力。
但是它的使用有点绕人,如果不正确使用,会带来意想不到的问题——比如await之后一直在等待,等到花儿也谢了,也等不来。
这篇博文将向你展示我们在实际开发中遇到的这个问题。
先看一段ASP.NET MVC示例代码:
public class BlogController : Controller { public async Task<ActionResult> AwaitDemo() { var responseHtml = GetResponseHtml("http://www.cnblogs.com/"); return Content(responseHtml); } private string GetResponseHtml(string url) { return GetResponseContentAsync(url).Result; } private async Task<string> GetResponseContentAsync(string url) { var httpClient = new System.Net.Http.HttpClient(); var response = await httpClient.GetAsync(url); if (response.StatusCode == System.Net.HttpStatusCode.OK) { return await response.Content.ReadAsStringAsync(); } else { return "error"; } } }
代码说明:
- 在上面的代码中,虽然在Action方法之前加了async Task<ActionResult>,但由于在方法体中没有使用await,所以实际还是以同步的方式执行的,与直接使用ActionResult是一样的。
- GetResponseHtml是同步方法,GetResponseContentAsync是异步方法,在GetResponseHtml中调用了异步的GetResponseContentAsync。(如果调用的是第三方程序集,我们就不知道在GetResponseHtml中进行了异步调用,所以这个方法的设计是有问题的)
这段代码执行结果会是怎样呢?
——结果就是没有结果,一直在执行。。。
(注:如果在控制台应用程序中调用同样的GetResponseHtml,不会出现这个问题)
那如果解决这个问题呢:
解决方法一:在MVC Action中开启一个Task进行await
public async Task<ActionResult> AwaitDemo() { var responseHtml = await Task.Factory.StartNew(() => GetResponseHtml("http://www.cnblogs.com/")); return Content(responseHtml); }
解决方法二:将GetResponseHtml变成异步方法
public async Task<ActionResult> AwaitDemo() { var responseHtml = await GetResponseHtml("http://www.cnblogs.com/"); return Content(responseHtml); } private async Task<string> GetResponseHtml(string url) { return await GetResponseContentAsync(url); }
显然,第2个解决方法是更好的。
所以,我们在设计一个方法(method)时,如果调用了async方法,一定要将这个方法本身设计为async的。不然,别人调用时很容易踩着这个坑,然后就一直等啊等。。。等到花儿谢了,电脑冒烟了,也等不到。
编程就是Debug
或许是之前玩电脑太凶,所以就把电脑当作纯粹的娱乐工具。大学时坚决选了个和计算机靠不上边的专业:物理。为了向父母表示拳拳的向学之心,自己连电脑都没带,就屁颠屁颠的研究欧拉方程去了。
但门外汉最开始接触编程,还是来自本科的课程。本科时有五门和计算机相关的课程:
- C语言。使用的是谭浩强的《C程序设计》。对教材不置可否,但这门课给我的印象是:程序员就是解决bug。再也不想转系到计算机学院了。
- 算法与数据结构。当时的教材是高教版的《算法与数据结构》。书里的例子使用的是伪代码。现在觉得这不算差,但当时上机的时候总是一堆bug要改。对C语言不算熟练,所以很多bug不容易解决。
- Fortran。教材是《FORTRAN语言》,谭浩强是作者之一。这是门古老的语言,但在科学运算方面运用很广,所以作为理科生必须要学习。和C语言给我的感觉类似,就是要不停的debug。痛苦极了。
- 微机原理。教材是《微型计算机原理与接口技术》。主讲硬件原理和汇编语言。当时用UltraEdit加插件,来运行汇编语言。学习汇编时,也只是试着用了用加法、移动之类的语法,没有具体的项目。
- 数据库。教材是本绿皮的,题目忘记了。还能记住的,就是不断分拆关系型数据库的表格,直到它满足某个范式。基本上没有太多实践。
回顾大学的这些课程,其实都算是计算机科学中相当重要的内容。问题是,这些课程都比较基础,偏理论,却轻实践。教材和老师的讲解也是如此,对于年轻人来说,乐趣少了些。具体的项目基本没有,自己很难看到效果。一时看不出这些课程的用途,就为了应付考试,将就的学吧。大好时光,还是挥洒在篮球场上吧。
大学期间还是出于兴趣,看了些Java和web的内容。Java看了《Core Java》的第一卷,但自己没有电脑,没法实践。还没把握到面向对象的精髓,就被轰然到来的期末备考裹挟走了。为了给女孩子惊喜,跟着w3cschool的教程,尝试做网站。学了html,css和php,但没钱租服务器,最后做了俩静态页面,差强人意。窘迫的年轻人。
创造才是乐趣
大学最后一年做毕业论文,阴差阳错,选了个三维重构流体运动的课题。这和计算机图形学沾上了边。当时用IDL来做图形处理和矩阵运算,可以很快看到算法对图形的处理效果。我一下子来了兴趣,连着两个假期都扑在这上面。空闲的时候,也是调程序、实验算法,看看结果如何。尽管导师评价代码太乱,不适合搞计算机,自己也是嘿嘿一笑,依然乐在其中。为了解决问题,自己还学了不少计算机图形学的内容,比如《图像处理、分析与机器视觉》。果然,兴趣是最大的学习动力。
在这期间,另一个重要的变化是接触Linux。为了做课题,我把自己的笔记本带到了学校。三年不见,这个本已经从曾经的“高富帅”变成了“矮挫熊”。听从朋友的建议,忍痛把操作系统换成了Ubuntu,以减少死机的次数。网上填个表,就有一张免费的cd寄到,顿时体验到开源的优越性。用了一段时间,总体感觉是,免费的果然差一些。比如Ubuntu上的办公软件就差office好多,更别说做的惨不忍睹的游戏了。唯一方便的是,学校里有一个Ubuntu镜像,所以可以以无比迅速的节奏来下载更新或者安装应用。真正享受Linux,还要等到未来。
需求的倒逼
本科毕业时,那所大学校园里快要溢出来的科研气氛给我打了鸡血。内心想的纯粹是搞科学研究。所以没怎么犹豫,就开始读博了。做的课题是流体计算相关的,因此需要在高性能电脑上并行运行。
高性能计算机的运行环境和普通电脑完全不同。首先,它安装的是CentOS,还没有任何的图形华界面,文本方面基本用vim。其次,由于要和别人竞争使用,要比较清楚的估计自己的工作量、所需的CPU数目和运行时间,还要查看空闲的资源,见缝插针。一个任务交上去,短的跑几天,长的跑几个星期。懒惰是创新的动力。为了不操那么多心,就写了些bash脚本来处理这些繁杂的事务,或者监视集群的运行状况。这才意识到bash和Linux工具(比如sed, awk, grep...)的好处。这期间读了《Linux Administration Handbook》,非常全面的一本Linux参考书,写的也很有趣。最后,高性能计算机是个并行的集群,需要了解并行算法和接口,所以读了《Parallel Programming with MPI》。
仅仅了解Linux的管理是不够的。在计算机上运行的是数值模型。这些数值模型是C语言和Fortran混合编写的。为了理解程序,认真读了《The C Programming Language 》,《Expert C Programming》,《Fortran 90/95 for Scientists and Engineers》。这几本书的好处是简洁且重点清晰,读起来不费劲。然而,在集群上的编译连接很成问题。主流的编程可以依赖StackOverflow。但数值运算的很多问题太偏门,在网上找不到资料。一封询问邮件发出去,基本得不到什么有用的回应。几番折腾下来,心里发狠,还不如自己读源代码,自己解决问题。因此读了《Advanced Programming in the Unix Environment》(好一本厚书,读的过程不堪回首,读完真的学到很多)。这些基础知识帮助我解决了不少编译连接方面的问题。
数据处理是另一个问题。在工作最开始使用的是Matlab,但研究所里的许可证有限,有时要等到别人用完了才能去用。再加上Matlab的许多附加包价格不菲,也让我觉得不方便。有一次和教授聊起这个问题,教授说,那你可以试试Python。Python,以及Python下的Numpy和Scipy包可以满足我的需求。而且想想,Python是免费的,这无论对我,还是对未来可能雇佣我的研究机构来说,都可以省下笔钱。这么看,学Python是个蛮靠谱的事情。《Learning Python》是本很全面的Python教材。
写作的动力
出于分享Python心得的目的,也为了打发空闲的时间,开始在博客园写“Python快速教程”。写到标准库,发现Linux系统知识是必备的背景知识,所以重开了“Linux的概念与体系”系列。另一方面,在写网络相关的包时,发现自己对网络协议方面了解太少。《TCP/IP Illustrated》里有对网络协议非常全面的介绍。这里面学到的东西,也构成我的“协议森林”系列的文章基础。自己的文章得到认可,也更有动力去多看多学了。
在和其它博主交流时,感觉到自己在基础知识方面,还是有很大的差距。毕竟自己是个非计算机专业的“杂牌军”。一是对面向对象的本质了解不够,这在《Thinking in Java》里脑补了一下。二是算法和数据结构的知识太肤浅,因此基于《Data Structures and Algorithm Analysis in C》,自己实现了一系列的经典算法。三是没有设计数据库的实际经验,正在努力做一个项目,来获得实际经验。看看这三点,都是本科时候学残了的课。不是不报,时候未到啊。
门外汉的徘徊
从小屁孩时,拿着鼠标小心翼翼的点“开始”,自己还真的时徘徊了许久。幸运的是,人生几个转弯下来,我依然喜欢编程,喜欢静静的计算机打交道。有一件两件真心喜欢的事情,就是很大的幸福了。和许多专业的计算机人士相比,我依然是一个门外汉。这种门外汉的徘徊,其实感觉不坏。作为门外汉,没有要成为最好的负担,只用随心所欲的享受技术和写作。
作为门外汉,好的技术书和好的工具会有很大的影响。毕竟,门外汉说来就来,也说走就走,很容易一时的不享受而放弃。不能不说,是那些文辞优美又简洁的技术书,让我感受到编程的优美。而Ubuntu下方便免费的编程环境,铺平了自由尝试的道路。现在更方便的是,我们可以在互联网上找到各种各样的教程、资料和公开课。许多云平台工具也是免费的。所以,即时是门外汉,也可以很容易跨过那道大门。这是门外汉最好的时代了。
最后附一张图,开启我门外汉生活的电脑: