使用C#迭代器实现协同路由的(Mis)陷阱

使用C#迭代器实现协同路由的(Mis)陷阱,c#,wcf,silverlight-3.0,asynchronous,yield,C#,Wcf,Silverlight 3.0,Asynchronous,Yield,我正在编写重构Silverlight程序的代码,以使用WCF服务中现有业务逻辑的一部分。在这样做的过程中,我遇到了Silverlight 3中的限制,即只允许对WCF服务的异步调用,以避免长时间运行或无响应的服务调用阻塞UI线程的情况(SL有一个有趣的队列模型用于在UI线程上调用WCF服务) 因此,编写曾经简单易懂的内容变得越来越复杂(请参阅问题末尾的代码示例) 理想情况下,我会使用它来简化实现,但遗憾的是,C#目前不支持将协同路由作为本机语言工具。然而,C#确实有生成器(迭代器)的概念,使用产

我正在编写重构Silverlight程序的代码,以使用WCF服务中现有业务逻辑的一部分。在这样做的过程中,我遇到了Silverlight 3中的限制,即只允许对WCF服务的异步调用,以避免长时间运行或无响应的服务调用阻塞UI线程的情况(SL有一个有趣的队列模型用于在UI线程上调用WCF服务)

因此,编写曾经简单易懂的内容变得越来越复杂(请参阅问题末尾的代码示例)

理想情况下,我会使用它来简化实现,但遗憾的是,C#目前不支持将协同路由作为本机语言工具。然而,C#确实有生成器(迭代器)的概念,使用
产生返回
语法。我的想法是重新使用yield关键字,以便为相同的逻辑构建一个简单的协同程序模型

然而,我不愿意这样做,因为我担心可能会有一些我没有预料到的隐藏(技术)陷阱(考虑到我在Silverlight和WCF方面相对缺乏经验)。我还担心,未来的开发人员可能不清楚实现机制,可能会阻碍而不是简化他们将来维护或扩展代码的工作。我已经看到了关于重新使用迭代器来构建状态机的问题:,虽然这与我正在做的事情并不完全相同,但它确实让我停了下来

但是,我需要做一些事情来隐藏服务调用的复杂性,并管理这类更改中的工作和潜在缺陷风险。我愿意接受我可以用来解决这个问题的其他想法或方法

代码的原始非WCF版本如下所示:

void Button_Clicked( object sender, EventArgs e ) {
   using( var bizLogic = new BusinessLogicLayer() ) {
       try  {
           var resultFoo = bizLogic.Foo();
           // ... do something with resultFoo and the UI
           var resultBar = bizLogic.Bar(resultFoo);
           // ... do something with resultBar and the UI
           var resultBaz = bizLogic.Baz(resultBar);
           // ... do something with resultFoo, resultBar, resultBaz
       }
   }
}
重新分解的WCF版本变得更加复杂(即使没有异常处理和前/后条件测试):

上面的代码显然是一种简化,因为它省略了异常处理、空性检查以及生产代码中需要的其他实践。尽管如此,我认为它展示了Silverlight中异步WCF编程模型开始出现的复杂性的快速增加。重新分解原始实现(它没有使用服务层,而是将其逻辑嵌入到SL客户机中)很快看起来是一项艰巨的任务。而且很可能非常容易出错

代码的共同例程版本类似于这样(我还没有测试过):

void按钮\u已单击(对象发送者,事件参数e){
执行步骤(按钮包括CLICKCOROUTINE);
}
私有IEnumerable按钮CLICKCOROUTINE(){
使用(var service=new SomeServiceClient()){
食物反应结果;
BarResponse resultBar;
BazResponse-resultBaz;
收益率()=>{
service.footcompleted=r=>NextStep(r,out resultFoo);
service.beginfo();
};
收益率()=>{
//使用resultFoo做一些UI工作
service.BarCompleted=r=>NextStep(r,out resultBar);
service.BeginBar();
};
收益率()=>{
//使用resultBar执行一些UI操作
service.BazCompleted=r=>NextStep(r,out resultBaz);
service.BeginBaz();
};
收益率()=>{
//使用resultFoo、resultBar、resultBaz进行一些处理
}
}
}
私有void NextStep(T result,out T store){
存储=结果;
PerformSteps();//继续迭代步骤
}
私有IEnumerable m_StepsToPerform;
私有void性能步骤(IEnumerable步骤){
m_StepsToPerform=步数;
PerformSteps();
}
私有void PerformSteps(){
如果(m_StepsToPerform==null)
return;//无事可做
m_StepsToPerform.MoveNext();
var nextStep=m_StepsToPerform.Current;
if(nextStep==null){
m_StepsToPerform.Dispose();
m_StepsToPerform=null;
return;//步骤结束
}
下一步();
}
在上面的代码中有各种各样的东西需要改进。但基本前提是,在执行每个步骤时(基本上是在最后一个异步WCF调用完成时),允许WCF的基于事件的异步模型驱动继续模式(为异常处理和各种检查创建一个拦截点)。虽然从表面上看,这看起来像是更多的代码,但值得一提的是,
PerformSteps()
NextStep()
是可重用的,只有
ButtonClickCoRoutine()
中的实现会随着每个不同的实现站点而改变


我不完全确定我是否喜欢这个模型,如果有一种更简单的方法来实现它,我也不会感到惊讶。但我在“互联网站”或MSDN或其他任何地方都找不到。提前感谢您的帮助。

您一定要查看。它使用迭代器正是为了这个目的

另一方面,您还应该了解它的连续性及其方法。并行扩展是.NET4.0的一部分,而CCR需要单独的许可。我建议你使用一个由吃、呼吸和睡觉的人编写的框架。你自己很容易弄错细节。

提供了一个更干净的模型来处理这个问题


它们提供了扩展,允许您以一种非常干净的方式针对异步事件编写简单的委托。我建议你调查一下,让他们适应这种情况。

我没有读到你的全部内容

他们在CCR robotics studio中使用此策略,许多其他项目也使用此策略。另一种方法是使用LINQ,请参见,例如,了解说明。反应式框架(Rx)是沿着这些路线构建的

Luca在他的文章中提到,C#/VB的未来版本可能会添加异步pr
// fields needed to manage distributed/async state
private FooResponse m_ResultFoo;  
private BarResponse m_ResultBar;
private BazResponse m_ResultBaz;
private SomeServiceClient m_Service;

void Button_Clicked( object sender, EventArgs e ) {
    this.IsEnabled = false; // disable the UI while processing async WECF call chain
    m_Service = new SomeServiceClient();
    m_Service.FooCompleted += OnFooCompleted;
    m_Service.BeginFoo();
}

// called asynchronously by SL when service responds
void OnFooCompleted( FooResponse fr ) {
    m_ResultFoo = fr.Response;
    // do some UI processing with resultFoo
    m_Service.BarCompleted += OnBarCompleted;
    m_Service.BeginBar();
}

void OnBarCompleted( BarResponse br ) {
    m_ResultBar = br.Response;
    // do some processing with resultBar
    m_Service.BazCompleted += OnBazCompleted;
    m_Service.BeginBaz();
} 

void OnBazCompleted( BazResponse bz ) {
    m_ResultBaz = bz.Response;
    // ... do some processing with Foo/Bar/Baz results
    m_Service.Dispose();
}
void Button_Clicked( object sender, EventArgs e ) {
    PerformSteps( ButtonClickCoRoutine );
}

private IEnumerable<Action> ButtonClickCoRoutine() {
    using( var service = new SomeServiceClient() ) {
        FooResponse resultFoo;
        BarResponse resultBar;
        BazResponse resultBaz;

        yield return () => {
            service.FooCompleted = r => NextStep( r, out resultFoo );
            service.BeginFoo();
        };
        yield return () => {
            // do some UI stuff with resultFoo
            service.BarCompleted = r => NextStep( r, out resultBar );
            service.BeginBar();
        };
        yield return () => {
            // do some UI stuff with resultBar
            service.BazCompleted = r => NextStep( r, out resultBaz );
            service.BeginBaz();
        };
        yield return () => {
            // do some processing with resultFoo, resultBar, resultBaz
        }
    }
}

private void NextStep<T>( T result, out T store ) { 
    store = result;
    PerformSteps();  // continues iterating steps
}

private IEnumerable<Action> m_StepsToPerform;
private void PerformSteps( IEnumerable<Action> steps ) {
   m_StepsToPerform = steps;
   PerformSteps();        
}

private void PerformSteps() {
   if( m_StepsToPerform == null ) 
       return; // nothing to do

   m_StepsToPerform.MoveNext();
   var nextStep = m_StepsToPerform.Current;
   if( nextStep == null ) {
       m_StepsToPerform.Dispose();
       m_StepsToPerform = null;
       return; // end of steps
   }
   nextStep();
}
// a sample client function that runs synchronously 
let SumSquares (client : IMyClientContract) = 
    (box client :?> IClientChannel).Open() 
    let sq1 = client.Square(3) 
    let sq2 = client.Square(4) 
    (box client :?> IClientChannel).Close() 
    sq1 + sq2 
// async version of our sample client - does not hold threads 
// while calling out to network 
let SumSquaresAsync (client : IMyClientContract) = 
    async { do! (box client :?> IClientChannel).OpenAsync() 
            let! sq1 = client.SquareAsync(3) 
            let! sq2 = client.SquareAsync(4) 
            do! (box client :?> IClientChannel).CloseAsync() 
            return sq1 + sq2 }