当前位置:首页 > 技术分析 > 正文内容

异步编程系列第04章 编写Async方法

ruisui883个月前 (02-14)技术分析15

写在前面

在学异步,有位园友推荐了《async in C#5.0》,没找到中文版,恰巧也想提高下英文,用我拙劣的英文翻译一些重要的部分,纯属娱乐,简单分享,保持学习,谨记谦虚。

如果你觉得这件事儿没意义翻译的又差,尽情的踩吧。如果你觉得值得鼓励,感谢留下你的赞,愿爱技术的园友们在今后每一次应该猛烈突破的时候,不选择知难而退。在每一次应该独立思考的时候,不选择随波逐流,应该全力以赴的时候,不选择尽力而为,不辜负每一秒存在的意义。

目录

编写Async方法

现在我们已经知道异步代码有多棒了,但是它到底难写吗?是时候来看一看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 DownloadStringTaskAsync(string address)

它的返回类型是Task。就像我在介绍Task这一小节的介绍,Task代表一个执行中的操作。并且它的子类Task代表着一个在将来某一时刻返回T类型的结果的操作。你可以认为Task承诺返回T类型的值在这个耗时操作之后。

Task和Task都可以代表异步操作,并且都有能力在操作完成后进行回调。在手动实现的异步方式中,你使用ContinueWith方法,传递一个委托,让代码在耗时操作结束后继续下一步操作。await使用相同的方式执行你的剩余的代码(也就是await之后的代码)。

如果你对Task运用await,他成为了一个await expression,并且整个表达式都拥有T类型。这意味着你可以等待一个变量的结果,并且可以在剩余的后半部分方法中使用,就像我们在例子中所看到的。然而当你await一个非泛型Task时,它保持await状态,但不能被分配给任何东西,就像调用一个void方法。这意味着,作为一个Task不承诺返回任何值,他仅仅表示操作本身。

await smtpClient.SendMailAsync(mailMessage);

没有什么可以把我们await表达式内部分开,所以我们可以直接的访问Task,或者在等待中做一些其他事情。具体看下代码和注释你就明白了。

Task myTask = webClient.DownloadStringTaskAsync(uri);
// Do something here
string page = await myTask;

完全理解它带来的启示很重要。DownloadStringTaskAsync方法在第一行执行,他开始在当前线程异步的执行,并且一旦开始了下载,它返回一个Task(对照上面的代码理解),依然在当前线程。只是在后来我们await Task时,编译器做了一些特别的事情。如果你把await写在和调用异步方法在一行代码里它一直是正确的。译者解释:也就是说如果调用await方法,在执行await内部操作的时候,这个线程是当前线程。执行await后的操作,可能是当前线程来处理后面的代码,也可能是新的线程来处理后面的代码。换种方式说,await所等待的方法,被当前线程来执行,但是执行时候立马回收到线程池,下一步操作随机选择一个线程来执行。因此我认为所有认为await会开新线程的说法是错误的。再强调一次,await后之所以会出现新的线程,是因为执行await内部的线程被回收到池子中,从线程池中再取出一个来执行下面的代码。await根本就没有开启线程的功能。

一旦调用DownloadStringTaskAsync发生,耗时操作开始执行,这同时给了我们一个很简单的方法来执行多个异步操作。我们可以开始多个操作,保持Tasks,然后await他们。

Task firstTask = webClient1.DownloadStringTaskAsync("http://oreilly.com");
Task secondTask = webClient2.DownloadStringTaskAsync("http://simple-talk.com");
string firstPage = await firstTask;
string secondPage = await secondTask;

等待多个Task是一种危险的方式,也许他们会抛出异常。如果两个操作抛出一个异常,第一个await将会传播它的异常,这意味着secondTask永远不会被等待。它的异常可能不会被注意到,还取决于.NET版本和设置,也许会丢失或者在另一个非预期线程中抛出,还可能终止该进程。我们将会在第七章讲到更好的方式去处理。

标记为async的方法有三种返回类型:

·void

·Task

·Task

没有其他允许的返回类型,因为通常再返回时方法都没执行结束。通常情况下,异步方法将会await一个耗时操作,意思是方法将会迅速返回,但是却在未来实现结果。也意味着,在方法返回时没有明确的结果,而是迟一些才会变得可用。

我会展示方法返回值之间的区别—例如,Task—这个返回类型,即编程人员打算返回给调用者的,在此情况下是string类型。通常,在非异步的方法中,返回类型和结果类型是一样的。但他们之间这样的不同对async方法很重要。

很明显void返回类型是很合理的选择在异步编程情况中。一个async void方法是一个“触发并忘记”的异步操作。调用者不能等待任何返回结果,并且不能知道操作什么时候结束或者是否成功。当你确定你不需要知道操作何时结束或者是否成功时,你应该使用void。async void最常见的应用场景是在async代码和其他代码的边界情况,比如UI事件处理必须返回void。

返回Task的异步方法允许调用者等待操作结束的结果,并且传递在异步代码执行期间的异常。当我们不需要任何返回类值时,一个async Task方法比async void方法更好,因为他允许调用者使用await去等待,并且处理异常更容易。(前面已经说到void最适合的情况)。

最后,返回Task的异步方法,像Task,通常用于异步操作需要返回值的时候。

async关键字出现在方法的声明上,就像public和static一样。尽管如此,async不能用于方法的签名,无论是重写方法,实现接口还是被调时。

async关键字唯一的影响是在他所应用的方法内部编译,而不像其他关键字,决定其如何与外界交互。正因如此,在关于重写方法,定义接口的规则上完全不被理会。

class BaseClass
{
     public virtual async Task AlexsMethod
    {
     ...
    }
}
class SubClass : BaseClass
{
    // This overrides AlexsMethod above
 public override Task 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 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 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> getNumberAsync = async delegate { return 3; };

下面是async lambda:

Func> getWordAsync = async  => "hello";

和普通的异步代码规则没什么不一样。你可以用他们来保持代码清晰整洁,捕捉闭合,和非异步方法以完全相同的形式书写。

写在最后

最近好迷茫,可能有点太急躁,总觉得高不成低不就,拼命地想越走越高,又看不到自己明显的进步。痛苦。

下一章节将介绍 await究竟做了什么。

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/1951.html

标签: aysnc
分享给朋友:

“异步编程系列第04章 编写Async方法” 的相关文章

Linux发行版需要杀软吗?卡巴斯基推出免费KVRT病毒扫描清理工具

IT之家 6 月 4 日消息,你认为使用 Linux 发行版,需要杀毒软件吗?或许很多用户认为 Linux 发行版偏小众,因此受到黑客攻击的风险也相对较小,不过卡巴斯基并不这么认为,近期推出了适用于 Linux 平台的杀毒软件。最新上线的 Linux 版本 Kaspersky Virus Remov...

快速掌握 Git:程序员必会的版本控制技巧

在现代软件开发中,版本控制系统(VCS)是开发人员不可或缺的工具。无论是个人项目,还是多人协作的团队开发,良好的版本控制都能确保代码管理的高效性与稳定性。而在版本控制系统中,Git 凭借其分布式、灵活性和高效性,成为了最流行的工具之一。几乎所有的开发团队都在使用 Git 来管理代码版本、协作开发和追...

抖音 Android 性能优化系列:启动优化实践

启动性能是 APP 使用体验的门面,启动过程耗时较长很可能使用户削减使用 APP 的兴趣,抖音通过对启动性能做劣化实验也验证了其对于业务指标有显著影响。抖音有数亿的日活,启动耗时几百毫秒的增长就可能带来成千上万用户的留存缩减,因此,启动性能的优化成为了抖音 Android 基础技术团队在体验优化方向...

身体越柔软越好?刻苦拉伸可能反而不健康 | 果断练

坐下伸直膝盖,双手用力向前伸,再用力……比昨天前进了一厘米,又进步了! 这么努力地拉伸,每个人都有自己的目标,也许是身体健康、线条柔美、放松肌肉、体测满分,也可能为了随时劈个叉,享受一片惊呼。 不过,身体柔软,可以享受到灵活的福利,也可能付出不稳定的代价,并不是越刻苦拉伸越好。太硬或者太软,都不安全...

有效地简化导航-Part 1:信息架构

「四步走」——理想的导航系统要做一个可用的导航系统,网页设计师必须按顺序回答以下4个问题:1. 如何组织内容?2. 如何解释导航的选项?3. 哪种导航菜单最适合容纳这些选项?4. 如何设计导航菜单?前两个问题关注构建和便签内容,通常称为信息架构。信息架构师通常用网站地图(site map diagr...

12种JavaScript中最常用的数组操作整理汇总

数组是最常见的数据结构之一,我们需要绝对自信地使用它。在这里,我将列出 JavaScript 中最重要的几个数组常用操作片段,包括数组长度、替换元素、去重以及许多其他内容。1、数组长度大多数人都知道可以像这样得到数组的长度:const arr = [1, 2, 3]; console.log(a...