浅谈 C# 中的代码协同 (Coroutine) 执行支持

原文: http://www.blogcn.com/User8/flier_lu/index.html?id=3409568

几个月前我曾大致分析过 C# 2.0 中 iterator block 机制的实现原理, 《C# 2.0 中Iterators的改进与实现原理浅析》 ,文中简要介绍了 C# 2.0 是如何在不修改 CLR 的前提下由编译器,通过有限状态机来实现 iterator block 中 yield 关键字。
实际上,这一机制的最终目的是提供一个代码协同执行的支持机制。
| 以下内容为程序代码:

using System.Collections.Generic;

public class Tokens : IEnumerable

  1<string>   
  2{   
  3public IEnumerator<string> GetEnumerator()   
  4{   
  5for(int i = 0; i<elements.length; #include="" (iterator)="" (part="" (string="" **以下内容为程序代码:**="" -="" ---="" ...="" 1)="" 1,="" 1.0="" 2="" 2)="" 2.0="" <vector="" =~="" ^.*.txt$="" blog="" c#="" c++="" console.writeline(item);="" def="" dir+"\"+entry="" dir+"\"+file}="" dir.chdir(".."[img]="" dir.chdir(dir)="" dir["*"].each="" do="" elements="" elements[i];="" end="" entry="" file="" filetest.directory?(entry)="" foreach="" getenumerator="" gray="" i++)="" if="" images="" img]="" in="" item="" iterator="" iterators="" new="" puts="" python)介绍了="" ruby="" spark="" stl="" textfiles(dir)="" textfiles(entry){|file|="" textfiles(“c:\”){|file|="" to="" tokens())="" up="" using="" warming="" wink.gif[="" yield="" {="" |="" |entry|="" }="" 上有一个系列文章介绍了协同执行的一些概念。="" 中完全类似的语法实现协同执行支持。="" 中实现协同执行:="" 中的独占式多任务倒是与协同模型比较类似。="" 中的迭代器="" 例如上面这段="" 函数体实际上是在同一个线程中交替执行的。这是一种介于线程和顺序执行之间的协同执行模式,之所以称之为协同(coroutine),是因为同时执行的多个代码块之间的调度是由逻辑隐式协同完成的。顺序执行无所谓并行性,而线程往往是由系统调度程序强制性抢先切换,相对来说win3.x="" 和="" 在其="" 在这段代码执行过程中,foreach="" 对="" 就协同执行而言,从功能上可以分为行为、控制两部分,控制又可进一步细分为控制逻辑和控制状态。行为对应着如何处理目标对象,如上述代码中:行为就是将目标对象打印到控制台;控制则是如何遍历这个="" 数组,可进一步细分为控制逻辑(顺序遍历)和控制状态(当前遍历到哪个元素)。下面将按照这个逻辑介绍不同语言中如何实现和模拟这些逻辑。="" 文章第="" 机制是如何简化遍历操作的代码。实际上中心思想就是将行为与控制分离,由语言层面的支持来降低控制代码的薄记工作。="" 的循环体和="" 的递归目录处理代码中,就采用了与="" 自身必须保存了与遍历容器相关的位置信息。例如在="" 语言(语法类似="" 这类不支持协同执行的语言,协同执行过程中的状态迁移或者说执行绪的调度工作,需要由库和使用者自行实现,例如="" 部分以="">   
  6#include <algorithm>   
  7#include <iostream>   
  8  
  9// The function object multiplies an element by a Factor   
 10template <class type="">   
 11class MultValue   
 12{   
 13private:   
 14Type Factor; // The value to multiply by   
 15public:   
 16// Constructor initializes the value to multiply by   
 17MultValue ( const Type&amp; _Val [img]/images/wink.gif[/img] : Factor ( _Val [img]/images/wink.gif[/img] {   
 18}   
 19  
 20// The function call for the element to be multiplied   
 21void operator ( [img]/images/wink.gif[/img] ( Type&amp; elem [img]/images/wink.gif[/img] const   
 22{   
 23elem *= Factor;   
 24}   
 25};   
 26  
 27int main( [img]/images/wink.gif[/img]   
 28{   
 29using namespace std;   
 30  
 31vector <int> v1;   
 32  
 33//...   
 34  
 35// Using for_each to multiply each element by a Factor   
 36for_each ( v1.begin ( [img]/images/wink.gif[/img] , v1.end ( [img]/images/wink.gif[/img] , MultValue<int> ( -2 [img]/images/wink.gif[/img] [img]/images/wink.gif[/img];   
 37}   
 38  
 39---  
 40  
 41虽然 STL 较为成功的通过迭代器、算法和谓词,将此协同执行逻辑中的行为和控制分离,谓词表现行为(MultValue<int> ![](http://www.blogcn.com/images/wink.gif) 、迭代器(v1.being(), v1.end())表现控制状态、算法表现控制逻辑(for_each),但仍然存在编写复杂,使用麻烦,并且语义不连冠的问题。   
 42一个缓解的方法是将谓词的定义与控制部分合并到一起,就是类似 boost::Lambda 的实现思路:   
 43|  **以下内容为程序代码:**   
 44  
 45for_each(v.begin(), v.end(), _1 = 1);   
 46  
 47for_each(vp.begin(), vp.end(), cout &lt;&lt; *_1 &lt;&lt; ' ');   
 48  
 49---  
 50  
 51通过神奇的模板和宏,可以一定程度降低编写独立谓词来定义行为的复杂度。但控制部分的状态和逻辑还是需要单独实现。   
 52  
 53而 C# 1.0 中就干脆没有自带支持,必须通过 《C# 2.0 中Iterators的改进与实现原理浅析》 一文中所举例子那样笨拙的方式完成。   
 54|  **以下内容为程序代码:**   
 55  
 56public class Tokens : IEnumerable   
 57{   
 58public string[] elements;   
 59  
 60Tokens(string source, char[] delimiters)   
 61{   
 62// Parse the string into tokens:   
 63elements = source.Split(delimiters);   
 64}   
 65  
 66public IEnumerator GetEnumerator()   
 67{   
 68return new TokenEnumerator(this);   
 69}   
 70  
 71// Inner class implements IEnumerator interface:   
 72private class TokenEnumerator : IEnumerator   
 73{   
 74private int position = -1;   
 75private Tokens t;   
 76  
 77public TokenEnumerator(Tokens t)   
 78{   
 79this.t = t;   
 80}   
 81  
 82// Declare the MoveNext method required by IEnumerator:   
 83public bool MoveNext()   
 84{   
 85if (position &lt; t.elements.Length - 1)   
 86{   
 87position++;   
 88return true;   
 89}   
 90else   
 91{   
 92return false;   
 93}   
 94}   
 95  
 96// Declare the Reset method required by IEnumerator:   
 97public void Reset()   
 98{   
 99position = -1;   
100}   
101  
102// Declare the Current property required by IEnumerator:   
103public object Current   
104{   
105get // get_Current函数   
106{   
107return t.elements[position];   
108}   
109}   
110}   
111...   
112}   
113  
114---  
115  
116这种笨拙的 IEnumerable 接口实现方法,实际上是将 STL 中提供控制状态的 iterator 完全自行实现,而且控制逻辑还限定于编写 IEnumerable 接口实现时的定义。就算可以通过策略 (Strategy) 模式提供一定程度的定制,但其代码逻辑过于分散,要理解一个简单调用必须查看四五处分散的代码。   
117  
118好在牛人总是不缺的,呵呵。   
119  
120Ajai Shankar 在 MSDN 上一篇非常出色的文章,  COROUTINES Implementing Coroutines for .NET by Wrapping the Unmanaged Fiber API  ,里面通过 Win32 API 的纤程 (Fiber) 支持和 CLR 几个底层 API 的支持,完整的实现了一套可用的协同执行支持机制。   
121Spark Gray 的第 4 篇文章中就详细讨论了这种实现方式的利弊:   
122  
123SICP, Fiber api and ITERATORS !(Part 4)   
124  
125纤程 Fiber 是 Win32 子系统为了移植 Unix 下伪线程环境下的程序方便,而提供的一套轻量级并行执行机制,由程序代码自行控制调度流程。   
126其使用方法很简单,在某个线程中调用 ConvertThreadToFiber(Ex) 初始化纤程支持,然后调用 CreateFiber(Ex) 建立多个不同纤程,对新建的纤程和转换时当前线程缺省纤程,都可以通过 SwitchToFiber 显式进行调度。   
127|  **以下内容为程序代码:**   
128  
129static int array[3] = { 0, 1, 2 };   
130  
131static int cur = 0;   
132  
133VOID CALLBACK FiberProc(PVOID lpParameter)   
134{   
135for(int i=0; i<sizeof(array) cur="array[i];" fiberfor="CreateFiber(0," fibermain="ConvertThreadToFiber(NULL);" fibermain);="" fiberproc,="" i++)="" lpvoid="" sizeof(array[0]);="" switchtofiber(lpparameter);="" while(cur="" {="" }="">= 0)   
136{   
137std::cout &lt;&lt; cur &lt;&lt; std::endl;   
138  
139SwitchToFiber(fiberFor);   
140}   
141  
142DeleteFiber(fiberFor);   
143  
144---  
145  
146上述伪代码是纤程使用的一个大概流程,可以看出实际上纤程跟上面 Ruby 和 C# 2.0 中的协同执行所需功能是非常符合的。而在实现上,纤程实际上是通过在同一线程堆栈中构造出不同的区域(ConvertThreadToFiber/CreateFiber),在 SwitchToFiber 函数中切换到指定区域,以此区域(纤程)的代码和寄存器等环境执行,有点类似于 C 代码库中 longjmp 的概念。Netscape 提供的状态线程库  State Threads library  就是通过 longjmp 等机制模拟的类似功能。   
147而在 .NET 1.0/1.1 中要使用纤程,则还需要考虑对每个纤程的 Managed 环境构造,以及切换调度时的状态管理等等。有兴趣的朋友可以仔细阅读上述两篇精彩文章。   
148|  **以下内容为程序代码:**   
149  
150class CorIter : Fiber {   
151protected override void Run() {   
152object[] array = new object[] {1, 2, 3, 4};   
153for(int ndx = 0; true; ++ndx)   
154Yield(arr[ndx]);   
155}   
156}   
157  
158Coroutine next = new CorIter();   
159Object o = next();   
160  
161---  
162  
163可以看到这个代码已经非常类似 C# 2.0 中的语法了,只是要受到一些细节上的限制。   
164  
165而 C# 2.0 中,大概是为了保障移植性,使用了将控制逻辑编译成状态机的方式实现,并由状态机自动管理控制状态。其原理我在 《C# 2.0 中Iterators的改进与实现原理浅析》 一文中已经大概分析过了,有兴趣的朋友可以进一步阅读 Spark Gray 的第 5 篇文章中的详细分析。   
166  
167Implementation of Iterators in C# 2.0 (Part 5)   
168  
169以及 Matt Pietrek 的关于 Iterator 状态机的分析文章   
170  
171Fun with Iterators and state machines    
172  
173  
174而为了将行为与控制更紧密地绑定到一起,C# 2.0 也提供了类似 C++ 中 boost::lambda 机制的匿名方法支持。简要的分析可以参考我以前的一篇文章 《CLR 中匿名函数的实现原理浅析》 ,或者 Spark Gray 的第 6 篇文章。   
175  
176Implementation of Closures (Anonymous Methods) in C# 2.0 (Part 6)</sizeof(array)></int></int></int></class></iostream></algorithm></elements.length;></string></string>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus