15.5.1【Task实现细节】 生成的代码

  还在吗?我们开始吧。由于深入讲解需上百页的篇幅,因此这里我不会讲得太深。但我会提 供足够的背景知识,以有助于你对整个结构的理解。之后可通过阅读我近些年来撰写的博客文章, 来了解更加错综复杂的细节,或简单地编写一些异步代码并反编译。同样地,这里我只介绍异步 方法,它包含了所有有趣的机制,并且不需要处理异步匿名函数所处的间接层。

  说明 警告,勇敢的旅行者—— 前方是实现细节! 本节将描述微软C# 5编译器(随着.NET 4.5的发布而推出)内实现的相关内容。从CTP版到beta版,有些细节变化很大,并且在未 来仍有可能发生改变。但我认为其基本理念并不会发生太大的变动。充分了解本节内容 后,你会发现并不存在什么魔法,只不过是一些编译器生成的聪明代码罢了。这之后便 可以从容应对未来变化的细节内容了。 正如我之前多次提到过的,它的实现(包括近似实现和真实编译器生成的代码)基本上可以 说是一个状态机。编译器将生成一个私有的内嵌结构,来表示这个异步方法。这个结构还必须包 含一个方法,其签名与所声明的方法签名相同。我称其为骨架方法,该方法本身没有多少内容, 但其他东西都依赖于它。

  骨架方法需要创建状态机,并执行一个步骤(此处的步骤指执行第一个 await 表达式之前的 代码),然后返回一个表示状态机进度的任务。(别忘了,在第一次到达真正需要等待的 await 表 达式之前,执行过程是同步的。)此后,骨架方法的运作就此结束。状态机会负责其余事项,后 续操作附加到其他异步操作后,可通知状态机去执行另一个步骤。当之前返回的任务被赋予适当 的值后,方法就执行到最后了, 状态机可随即发出信号。

  当然,“执行方法体中的代码”这一步,只有在骨架方法中第一次调用时,才会从方法的开 头执行。以后每次到达该块,都是由后续操作从之前中断的地方开始继续执行。 现在有两个概念需要关注,即骨架方法和状态机。在本节的剩余篇幅中,我将使用单个异步 方法作为示例,如代码清单15-11所示。

 1         static async Task<int> SumCharactersAsync(IEnumerable<char> text)
 2         {
 3             int total = 0;
 4             foreach (char ch in text)
 5             {
 6                 int unicpde = ch;
 7                 await Task.Delay(unicpde);
 8                 total += unicpde;
 9             }
10             await Task.Yield();
11             return total;
12         }

代码清单15-11没有什么实际意义,但我们只关注流控制。在开始之前,有必要指出以下几点。
 该方法包含一个参数( text )。
 该方法包含一个循环,后续操作执行时需跳回该循环内。
 该方法包含两个不同类型的 await 表达式: Task.Delay 返回一个 Task ,而 Task.Yield()则返回一个 YieldAwaitable 。
 该方法包含显式的局部变量( total 、 ch 和 unicode ),需在不同的调用间关注其变化。
 该方法包含一个通过调用 text.GetEnumerator() 方法创建的隐式局部变量。
 该方法最终返回一个值。
这段代码最初的版本将 text 作为 string 类型的参数,但C#编译器会对字符串的迭代进行优
化,并使用 Length 属性和索引器,这会使反编译后的代码变得更加复杂。

 

posted @ 2018-12-16 20:55  一只桔子2233  阅读(202)  评论(0编辑  收藏  举报