异步编程系列第03章 本身写异步代码

写在前面

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

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

   转载和爬虫请注明原文连接http://www.cnblogs.com/tdws/p/5628538.html,博客园 蜗牛 2016年6月27日。编程

目录

第01章 异步编程介绍app

第02章 为何使用异步编程dom

第03章 手动编写异步代码异步

    .NET中的一些异步模式
    最简单的异步模式
    关于Task的介绍
    手动编写异步代码的问题
    使用手写异步代码转换示例(第二章最后一个示例)
async

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

第05章 Await究竟作了什么工具

第06章 以Task为基础的异步模式性能

第07章 异步代码的一些工具

第08章 哪一个线程在运行你的代码

第09章 异步编程中的异常

第10章 并行使用异步编程

第11章 单元测试你的异步代码

第12章 ASP.NET应用中的异步编程

第13章 WinRT应用中的异步编程

第14章 编译器在底层为你的异步作了什么

第15章 异步代码的性能

手动编写异步代码

  在本章,咱们将会讨论一些关于不使用C#5.0关键字async的异步编程。这种方式虽然已是过去的技术,也许你不会再使用,但这对于你理解异步编程表象背后发生了什么事情是很重要的。也由于这一点,我将会很快的讲述示例,仅仅着重揭示出对你理解有帮助的地方。

 
.NET中的一些异步模式

  正如我以前提到的,Silverlight只提供了像web访问的异步版本API。这里有一个例子,你能够下载一个网页,并显示它:

private void DumpWebPage(Uri uri)
{
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += OnDownloadStringCompleted;
webClient.DownloadStringAsync(uri);
}
private void OnDownloadStringCompleted(object sender,
DownloadStringCompletedEventArgs eventArgs)
{
m_TextBlock.Text = eventArgs.Result;
}

  这种API是基于事件的异步模式(EAP)。这个想法是想替代单线程方法去下载网页,即阻塞型代码会一直等到下载结束再调用一个方法或触发一个事件。这个方法看起来和同步代码同样,除了无返回类型。这个事件也有一个特别的eventArgs类型,它包含值检索。

  咱们在调用这个方法前注册了事件。该方法当即返回,固然这是由于它是异步代码。而后在未来的某个时刻触发。这种模式显然很复杂,不只仅是由于你要将它分红像例子同样的两个方法。最重要的是,你注册了一个时间增长了复杂性。若是说我还要用相同的WebClient实例处理其余需求,那么你也许不但愿这个时间依然被附加着而且再次执行一遍。

  在.NET功能中另外一个异步模式设计IAsyncResult接口。其中一个例子就是DNS查找主机名的IP地址,BeginGetHoseAddress。这种设计要求两个方法,一个是开始执行的BeginMethodName,另外一个是执行结束EndMethodName,即你的回调方法。

private void LookupHostName()
{
object unrelatedObject = "hello";
Dns.BeginGetHostAddresses("oreilly.com", OnHostNameResolved, unrelatedObject);
}
private void OnHostNameResolved(IAsyncResult ar)
{
object unrelatedObject = ar.AsyncState;
IPAddress[] addresses = Dns.EndGetHostAddresses(ar);
// Do something with addresses
...
}

  至少这种方式不会遭受残留注册事件的影响,然而这也额外的对API增长了复杂性。有两个方法而不是一个,我以为很不天然。

  这两种异步模式都须要你分为两个方法来书写。IAsyncResult模式要你从第一个方法中向第二个方法传递某些参数,就像我传递了string类型的"hello"。可是这种方式很复杂,即便你不须要这个参数,仍是不得不传递它,而且迫使你转换为object类型。

 
最简单的异步模式

  能够说下面这段代码拥有异步行为,即便不使用async关键字,也不用向方法传递委托:

void GetHostAddress(string hostName, Action<IPAddress> callback)

  我发现这种方式比其余方式更加易用。

private void LookupHostName()
{
GetHostAddress("oreilly.com", OnHostNameResolved);
}
private void OnHostNameResolved(IPAddress address)
{
// Do something with address
...
}

  不一样于两个方法的模式,像我之前提到的,使用异步方法或者用lambda表达式作回调。它拥有重要的好处就是能够在第一个方法中访问变量。

private void LookupHostName()
{
int aUsefulVariable = 3;
GetHostAddress("oreilly.com", address =>
{
// Do something with address and aUsefulVariable
...
});
}

  这个Lambda有一点难以阅读,而且一般若是你使用多重的异步编程,你将须要不少Lambda表达式相互嵌套,你的代码将会很快变得犬牙交错和难以处理。

  这种简单方法的缺点在于他们再也不对调用者抛出异常。在以前.NET异步编程中,调用EndMethodName或者获得Result属性时,将会从新抛出异常,因此在代码中咱们能够相应的处理异常。相反,他们可能在某个错误地方中止或者根本不去处理。

 
关于Task的介绍

  任务并行实在.NET Framework4.0版本中推出的。其最重要的地方是Task类,即表明一个正在执行的操做。 泛型版本的Task<T>, 当操做完成时返回类型为T的值。

   在C#5.0 async功能上咱们大量的使用了Task,咱们将会稍后讨论。然而即便没有async,你依然可使用Task,尤为是使用Task<T>来异步编程。这样作就行,你开始一个返回Task<T>的操做,而后使用ContinueWith方法注册你的回掉方法。

private void LookupHostName()
{
Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync("oreilly.com");
ipAddressesPromise.ContinueWith(_ =>
{
IPAddress[] ipAddresses = ipAddressesPromise.Result;
// Do something with address
...
});
}

  Task的优势就像这个DNS只须要一个方法,使API更加整洁。全部调用异步行为相关的逻辑均可在Task类当中,因此它不须要在每个方法里都进行复制。这个逻辑能够作不少重要的事儿,好比处理异常和同步上下文(SynchronizationContexts)。这些,咱们将会在第八章讨论,对于在一个特定线程上执行callback颇有用处(好比UI线程)。

  最重要的是,Task给咱们提供一种使用异步的相对抽象的操做方式。咱们能够利用这种组合型去编写咱们的工具,即在不少须要使用Task的状况下提供给一些有用的行为。咱们将会看到不少相关的工具组件(utilities)在第七章当中。

 
手动编写异步代码的问题

   正如咱们看到的,咱们有不少方式来实现异步编程。有一些方式比其余方式整洁易懂易用,可是也但愿你已经看出他们共有的缺陷。你打算写的程序不得不分为两个方法:实际的方法和回调方法。还有使用异步方法或嵌套屡次lambda表达式做为回调,使你的代码一环套一环难以理解。

  实际上这里还有另外一个问题。咱们已经说过调用一次异步方法的状况,可是当你须要多个异步时会发生什么呢?更糟糕的是,若是弄须要在循环中调用异步又会发生什么呢?你为一个方式是使用递归方法,这又比普通的循环难以阅读多了。

private void LookupHostNames(string[] hostNames)
{
LookUpHostNamesHelper(hostNames, 0);
}
private static void LookUpHostNamesHelper(string[] hostNames, int i) { Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync(hostNames[i]); ipAddressesPromise.ContinueWith(_ => { IPAddress[] ipAddresses = ipAddressesPromise.Result; // Do something with address ... if (i + 1 < hostNames.Length) { LookUpHostNamesHelper(hostNames, i + 1); } }); }

  哇!

  在这些异步编程方式中,引起的另外一个问题就是须要消耗大量代码。若是你写一些异步代码,指望在其余地方使用,你不得不提供API,若是API混乱或者忘记当时的初衷不能理解的话,将会事半功倍。异步代码是会“传染”的,所以不只你须要异步API,还影响调用者和调用者的调用者,知道整个程序乱成一团。

 
使用手写异步代码转换示例(第二章最后一个示例)

  再来谈谈第二章最后一个示例,咱们讨论了一个会因从网站下载icons,形成UI线程阻塞,并致使出现应用程序未响应的WPF UI app。如今咱们将会看到,将它转化成手写的异步代码。

  第首先要作的就是找到一个异步API的版本,我用(WebClient。下载文件)。正如咱们已经看到的,WebClient方法使用基于事件的异步方式(EAP),因此咱们能够在开始下载以前注册一个事件做为回调方法。

private void AddAFavicon(string domain)
{
WebClient webClient = new WebClient();
webClient.DownloadDataCompleted += OnWebClientOnDownloadDataCompleted;
webClient.DownloadDataAsync(new Uri("http://" + domain + "/favicon.ico"));
}
private void OnWebClientOnDownloadDataCompleted(object sender,
DownloadDataCompletedEventArgs args)
{
Image imageControl = MakeImageControl(args.Result);
m_WrapPanel.Children.Add(imageControl);
}

  固然,咱们的真正属于一块儿的逻辑要被分红两个方法。我不喜欢使用Lambda来代替刚才的EAP,由于lambda会出如今真正开始下载前,我以为这是不可读的。

  这个版本的示例也能够在线(https://bitbucket.org/alexdavies74/faviconbrowser)找到,(//译者注释:不运行此程序也不要紧,主要是体会下思路就好)在manual分支。若是你运行它,不进界面可相应,图标也会逐一出现。正所以,咱们也引入了一个bug,如今因为全部下载操做同时开始,icons的排序由其下载前后决定,而不是由个人前后请求来决定。若是你想检验本身是否理解手动编写异步代码,我建议你尝试着解决此bug。在orderedManual分支下(上面列出的站点下)提供了一个解决方案。其余更有效的解决方案也是有可能的。

写在后面
27号入职,花了三天的业余时间,坎坎坷坷的翻译了第三章。若是你对你有些许益处,不要吝啬你的赞,给个鼓励。不许确和须要补充的地方,也请前辈们不吝赐教,我将虚心改正。下一章将会介绍 “ 编写Async方法
相关文章
相关标签/搜索