C#-continuation-passing style(CPS)

    技术2022-05-20  57

    如果你还不是很了解CPS是什么,那么推荐几个链接给你(希望你的英语要给力啊):

    http://blogs.msdn.com/b/wesdyer/archive/2007/12/22/continuation-passing-style.aspx

    http://en.wikipedia.org/wiki/Continuation-passing_style

    http://blogs.msdn.com/b/ericlippert/archive/2010/10/22/continuation-passing-style-revisited-part-two-handwaving-about-control-flow.aspx

     

    CPS(continuation-passing style):字面可以理解为后继式传递格式,这是在函数式编程中的一个特性。但在C#中Lambda表达式和Action<T> 泛型委托结合起来,能够很好地实现这一特性。

     

    原来我们用结构化处理异常的方式:

    void Q(){  try   {     B(A());  }   catch   {     C();   }   D();}int A(int a){    throw;    return 0; // 不可达,暂时忽略

    }void B(int x) { //to do something }void C() { //to do something  }void D() { //to do something  }

     

    这些方法调用是否面向对象,这不是我们要讨论的话题。但是不管怎么说,try{...}catch{...}finally{...}这种结构化的异常处理方式,至今还是在被广泛使用。

     

    结构化的一个特点,耦合性强,关联度高。就像流水线一样紧密结合。

    让我们先看一下,这个调用流程:

    首先执行方法A,如果A的调用未产生任何异常,则返回结果给B作为参数,调用B方法,如果方法B执行正常。则方法C不会被执行,直接跳到方法D开始执行。

    如果方法A或B任何一方产生异常,执行将会被中止。调用将会跳转到方法C执行(当然前提是,该异常能被顺利捕获到)。最后再调用方法D。

     

    其实CLR在采用结构化的异常处理机制时,实现了一些帅选器和处理器等内部和语言机制,可参考《CLR Via C# 3.0》。但是,既然我们说这种异常处理方式是一种环环相扣的,类似于流式的,为什么我们不能模拟采用CPS来实现呢?

    对于ABCD四个方法,我们都考虑两种情况(其实就是一种if ...else....结构)一种情况:方法调用成功;一种情况方法调用失败。

    于是,可以这样定义:

    Action<T>:接受一个类型为T的参数,并且没有返回值;

    void A(Action<int> normal, Action error);

    void B(int x, Action normal, Action error) { whatever }void C(Action normal, Action error) { whatever }void D(Action normal, Action error) { whatever }

    注:所有的normal,都可以想象为,我们通常不考虑异常的方法体,所有的error都可以认为对原方法体中出现异常的处理方法

     

    这样对try块的处理逻辑抽象为:

    Try (       /* tryBody      */ (bodyNormal, bodyError)=>A(       /* normal for A */   x=>B(x, bodyNormal, bodyError),       /* error for A  */   bodyError),       /* catchBody    */ C,       /* outerNormal  */ ()=>D(qNormal, qError),       /* outerError   */ qError );首先,从外部来看try块只能有两个出口:

    由try——>outerNormal,将执行:()=>D(qNormal, qError)  用outerNormal

    try——>catchBody 将执行:()=>C(outerNormal,outerError)即为::()=>C(()=>D(qNormal, qError),outerError)

    而对于try体,则有:(bodyNormal, bodyError)=>A(x=>B(x, bodyNormal, bodyError), bodyError);

    因此,展开就为:

     

     

     

    A(  x=>B(               // A's normal continuation    x,                   // B's argument    ()=>D(            // B's normal continuation      qNormal,        // D's normal continuation      qError),        // D's error continuation    ()=>C(            // B's error continuation      ()=>D(          // C's normal continuation        qNormal,      // D's normal continuation        qError),      // D's error continuation      qError)),       // C's error continuation  ()=>C(              // A's error continuation    ()=>D(            // C's normal continuation      qNormal,        // D's normal continuation      qError),        // D's error continuation    qError))          // C's error continuation

     

    如果C抛出异常则,continuation立刻被转入qError执行;

    从可读性上来讲这当然不是一种非常好的实现方式。但这是一种思路,我们在编写一些前后关联性很强的方法调用时,并且方法调用不是很多时,可以采用这种做法。

     

    下面的实现很好地体现了CPS的流式调用(伪递归)和数据处理控制权的交接:

    实现Factorial:

    static void Main() { Factorial(5, x => Console.WriteLine(x)); } static void Factorial(int n, Action<int> k) { if (n == 0) k(1); else Factorial(n - 1, x => k(n * x)); } 当然CPS还有一个很有用的特性就是能够支持异步回调,在异步编程中很有用。


    最新回复(0)