异步编程系列第04章 编写Async方法
在学异步,有位园友推荐了《async in C#5.0》,没找到中文版,恰巧也想提高下英文,用我拙劣的英文翻译一些重要的部分,纯属娱乐,简单分享,保持学习,谨记谦虚。
如果你觉得这件事儿没意义翻译的又差,尽情的踩吧。如果你觉得值得鼓励,感谢留下你的赞,愿爱技术的园友们在今后每一次应该猛烈突破的时候,不选择知难而退。在每一次应该独立思考的时候,不选择随波逐流,应该全力以赴的时候,不选择尽力而为,不辜负每一秒存在的意义。
转载和爬虫请注明原文链接http://www.cnblogs.com/tdws/p/5645075.html,博客园 蜗牛 2016年6月27日。
现在我们已经知道异步代码有多棒了,但是它到底难写吗?是时候来看一看C#5.0的Async功能了。正如我们之前在第三章所看到的,一个async方法允许包含await关键字。
private asyncvoid DumpWebPageAsync(string uri) { WebClient webClient = new WebClient(); string page = awaitwebClient.DownloadStringTaskAsync(uri); Console.WriteLine(page); }
由于await关键字在此方法中,到await这里就不再继续向下执行,直到下载结束恢复处理。这种处理使此方法异步,在本章,我们将会探索这样的异步方法。
我们现在将之前那个示例转换成Async的。如果可以,打开原版的代码,在你向下阅读前,尝试将它转换成async和await的方法。
最重要的方法是AddFavicon,即,将下载后的icons添加到UI界面上的方法。我们想把它编程异步的,这样UI线程在下载期间就有空闲去相应用户的操作。第一步要做的就是添加async关键字到方法上。它和static关键字在一样的签名位置。
然后我们需要使用await等待下载。await在C#语法中扮演者医院运算符的角色,就像‘!’或者‘(type)转换操作符’。他被放置在一个表达式的左侧,意于异步的等待表达式。
最后,调用DownloadData方法必须替换成调用异步版本DownloadDataAsync。
Async方法不是自动做到异步的。Async方法仅仅是将调用(消耗consume)其它异步方法更加容易。他们同步地运行着,一直到调用异步方法和await它。当他们做这样的事情时,必须使自身变得异步。有时,一个async方法不await任何事情。
private asyncvoid AddAFavicon(string domain) { WebClient webClient = new WebClient(); byte[] bytes = awaitwebClient.DownloadDataTaskAsync("http://" + domain + "/ favicon.ico"); Image imageControl = MakeImageControl(bytes); m_WrapPanel.Children.Add(imageControl); }
比较一下这种方式和之前章节所介绍的版本。这看起来更像同步代码的样子。没有任何额外的方法,只在相同结构下有一点额外的代码。然而,他的行为和我们在上一章中的其中一小节(点击跳转)所写的版本很相像。
让我们来分解一下我们写的await吧。下面是WebClient.DownloadStringTaskAsync方法。
Task<string> DownloadStringTaskAsync(string address)
它的返回类型是Task<string>。就像我在介绍Task这一小节的介绍,Task代表一个执行中的操作。并且它的子类Task<T>代表着一个在将来某一时刻返回T类型的结果的操作。你可以认为Task<T>承诺返回T类型的值在这个耗时操作之后。
Task和Task<T>都可以代表异步操作,并且都有能力在操作完成后进行回调。在手动实现的异步方式中,你使用ContinueWith方法,传递一个委托,让代码在耗时操作结束后继续下一步操作。await使用相同的方式执行你的剩余的代码(也就是await之后的代码)。
如果你对Task<T>运用await,他成为了一个await expression,并且整个表达式都拥有T类型。这意味着你可以等待一个变量的结果,并且可以在剩余的后半部分方法中使用,就像我们在例子中所看到的。然而当你await一个非泛型Task时,它保持await状态,但不能被分配给任何东西,就像调用一个void方法。这意味着,作为一个Task不承诺返回任何值,他仅仅表示操作本身。
await smtpClient.SendMailAsync(mailMessage);
没有什么可以把我们await表达式内部分开,所以我们可以直接的访问Task,或者在等待中做一些其他事情。具体看下代码和注释你就明白了。
Task<string> myTask = webClient.DownloadStringTaskAsync(uri); // Do something here string page = await myTask;
完全理解它带来的启示很重要。DownloadStringTaskAsync方法在第一行执行,他开始在当前线程异步的执行,并且一旦开始了下载,它返回一个Task<string>(对照上面的代码理解),依然在当前线程。只是在后来我们await Task<string>时,编译器做了一些特别的事情。如果你把await写在和调用异步方法在一行代码里它一直是正确的。译者解释:也就是说如果调用await方法,在执行await内部操作的时候,这个线程是当前线程。执行await后的操作,可能是当前线程来处理后面的代码,也可能是新的线程来处理后面的代码。换种方式说,await所等待的方法,被当前线程来执行,但是执行时候立马回收到线程池,下一步操作随机选择一个线程来执行。因此我认为所有认为await会开新线程的说法是错误的。再强调一次,await后之所以会出现新的线程,是因为执行await内部的线程被回收到池子中,从线程池中再取出一个来执行下面的代码。await根本就没有开启线程的功能。
一旦调用DownloadStringTaskAsync发生,耗时操作开始执行,这同时给了我们一个很简单的方法来执行多个异步操作。我们可以开始多个操作,保持Tasks,然后await他们。
Task<string> firstTask = webClient1.DownloadStringTaskAsync("http://oreilly.com"); Task<string> secondTask = webClient2.DownloadStringTaskAsync("http://simple-talk.com"); string firstPage = await firstTask; string secondPage = await secondTask;
等待多个Task是一种危险的方式,也许他们会抛出异常。如果两个操作抛出一个异常,第一个await将会传播它的异常,这意味着secondTask永远不会被等待。它的异常可能不会被注意到,还取决于.NET版本和设置,也许会丢失或者在另一个非预期线程中抛出,还可能终止该进程。我们将会在第七章讲到更好的方式去处理。
标记为async的方法有三种返回类型:
·void
·Task
·Task<T>
没有其他允许的返回类型,因为通常再返回时方法都没执行结束。通常情况下,异步方法将会await一个耗时操作,意思是方法将会迅速返回,但是却在未来实现结果。也意味着,在方法返回时没有明确的结果,而是迟一些才会变得可用。
我会展示方法返回值之间的区别—例如,Task<string>—这个返回类型,即编程人员打算返回给调用者的,在此情况下是string类型。通常,在非异步的方法中,返回类型和结果类型是一样的。但他们之间这样的不同对async方法很重要。
很明显void返回类型是很合理的选择在异步编程情况中。一个async void方法是一个“触发并忘记”的异步操作。调用者不能等待任何返回结果,并且不能知道操作什么时候结束或者是否成功。当你确定你不需要知道操作何时结束或者是否成功时,你应该使用void。async void最常见的应用场景是在async代码和其他代码的边界情况,比如UI事件处理必须返回void。
返回Task的异步方法允许调用者等待操作结束的结果,并且传递在异步代码执行期间的异常。当我们不需要任何返回类值时,一个async Task方法比async void方法更好,因为他允许调用者使用await去等待,并且处理异常更容易。(前面已经说到void最适合的情况)。
最后,返回Task<T>的异步方法,像Task<string>,通常用于异步操作需要返回值的时候。
async关键字出现在方法的声明上,就像public和static一样。尽管如此,async不能用于方法的签名,无论是重写方法,实现接口还是被调时。
async关键字唯一的影响是在他所应用的方法内部编译,而不像其他关键字,决定其如何与外界交互。正因如此,在关于重写方法,定义接口的规则上完全不被理会。
class BaseClass { public virtual async Task<int> AlexsMethod() { ... } } class SubClass : BaseClass { // This overrides AlexsMethod above public override Task<int> AlexsMethod() { ... } }
接口不能使用async定义,很简单,因为没必要。如果一个借口需要方法返回Task,在实现时可以使用async,但是用不用还是方法自己的事儿。接口不需要特别声明出是否要异步。
Return Statement在异步方法中有着不同的行为。想想在普通的非异步方法,使用return statement依赖于方法的返回类型。
void方法
return statement只需要return;,并且是可选择。(不写也行)
返回一个T类型的方法
return必须有一个T类型的表达式,比如5+x,并且必须出现在方法所有路径的最后。
在一个标记为async的方法中,不同的情况也有不同的规则
void方法和返回Task的方法
return statement只需要return;,并且是可选择的。(不写也行)
返回Task<T>的方法
return必须返回一个T的表达式并且要在所有返回路径的最后。
在异步方法中,方法的返回类型和表达式类型有所不同。编译器转换可以被认为是将你的结果值包裹起来,在返回给调用者之前。当然,事实上Task>T<立即被创建,并且一旦在你的耗时操作结束后,将你的值“填充”上。译者:像前几章讲的一样,异步方法立即返回被“包裹”的值,在执行结束后,填充值。
正如我们所见,最好的使用由异步返回的Task的方式是在异步方法中await它。当你这样做时,你的方法通常也返回Task。为了享受异步风格的优势,你调用方法的代码必须不是阻塞地等待你的Task结束,并且这样的话,你的调用者很可能也在await你。
下面示例是一个我曾经写过的方法,用于读取一个网页中有多少个字符,并且异步的返回它们。
private async Task<int> GetPageSizeAsync(string url) { WebClient webClient = new WebClient(); string page = await webClient.DownloadStringTaskAsync(url); return page.Length; }
To use it, I need to write another async method, which returns its result asynchronously为了使用它,我需要写另一个异步的返回自己结果的异步方法,就像这样:
private async Task<string> FindLargestWebPage(string[] urls) { string largest = null; int largestSize = 0; foreach (string url in urls) { int size = await GetPageSizeAsync(url); if (size > largestSize) { size = largestSize; largest = url; } } return largest; }
在这种方式下,我们不用写异步的方法链,只是每次await就好。Async是一个传染性的编程模型,他可以很容易就弥漫到整个代码体系。但是我认为这正是由于async方法如此容易的书写,这完全没有问题。
普通的命名方法可以异步,并且有两种匿名方法一样可以异步。语法和正常的方法也很像。下面是如何使用异步匿名委托的示例:
Func<Task<int>> getNumberAsync = async delegate { return 3; };
下面是async lambda:
Func<Task<string>> getWordAsync = async () => "hello";
和普通的异步代码规则没什么不一样。你可以用他们来保持代码清晰整洁,捕捉闭合,和非异步方法以完全相同的形式书写。
最近好迷茫,可能有点太急躁,总觉得高不成低不就,拼命地想越走越高,又看不到自己明显的进步。痛苦。
下一章节将介绍 await究竟做了什么。