c# - (错误)使用 C# 迭代器实现协程的陷阱

标签 c# wcf silverlight-3.0 asynchronous yield

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

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

理想情况下,我会使用 coroutines为了简化实现,但遗憾的是,C# 目前不支持协程作为本地语言工具。但是,C# 确实有使用 yield return 的生成器(迭代器)的概念。句法。我的想法是重新调整 yield 关键字的用途,以允许我为相同的逻辑构建一个简单的协程模型。

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

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

代码的原始非 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 版本变得更加复杂(即使没有异常处理和前后条件测试):

// 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();
}

上面的代码显然是一种简化,因为它省略了异常处理、无效性检查和生产代码中必需的其他做法。尽管如此,我认为它证明了 Silverlight 中的异步 WCF 编程模型开始出现复杂性的快速增加。重构原始实现(它没有使用服务层,而是将其逻辑嵌入到 SL 客户端中)很快就会成为一项艰巨的任务。并且很可能很容易出错。

代码的协程版本看起来像这样(我还没有测试过):

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();
}

上面的代码有各种各样需要改进的地方。但基本前提是分解出延续模式(为异常处理和各种检查创建拦截点),同时允许 WCF 基于事件的异步模型在执行每个步骤时驱动 - 基本上是在最后一个异步 WCF 调用完成时。虽然从表面上看这看起来像是更多代码,但值得一提的是 PerformSteps()NextStep()是可重用的,只有在ButtonClickCoRoutine()中实现会随着每个不同的实现站点而变化。

我不确定我是否喜欢这个模型,如果有更简单的方法来实现它,我也不会感到惊讶。但是我无法在“interwebs”或 MSDN 或其他任何地方找到一个。预先感谢您的帮助。

最佳答案

你绝对应该看看 Concurrency and Coordination Runtime .它使用迭代器正是为了这个目的。

另一方面,你也应该看看Parallel Extensions及其延续的方法。 Parallel Extensions 是 .NET 4.0 的一部分,而 CCR 需要单独的许可。不过,我建议您使用由吃、呼吸和 sleep 这些东西的人编写的框架。自己很容易弄错细节。

关于c# - (错误)使用 C# 迭代器实现协程的陷阱,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1903531/

相关文章:

c# - 将审计和存档信息生成到由多个应用程序共享的 sql 数据库中的最简单方法

c# - Switch Cell 代码中的 Xamarin.Forms DataBinding

c# - 派生类上的不同静态成员值

wcf - 常见的 WCF 异常 : Connection Unexpectedly Closed

wcf - 不同类型的端点必须有多个 Mex 端点

Silverlight 3 滚动时的复选框列表框错误?

c# - 如何调节磁贴图标的旋转速度?

c# - Dispose() 方法中 GC.SuppressFinalize(this) 的目的是什么?

silverlight - 如何将事件聚合添加到现有 Silverlight 应用程序?

c# - 添加为服务引用时 WCF 消息协定缺少 header