(Mis)使用C#迭代器实现协同程序的缺陷

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

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

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

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

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

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

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

上面的代码显然是一种简化,因为它省略了exception处理,无效检查以及生产代码中必需的其他实践。 尽管如此,我认为它certificate了Silverlight中异步WCF编程模型开始出现的复杂性的快速增长。 重新分解原始实现(不使用服务层,而是将其逻辑嵌入SL客户端)正在迅速成为一项艰巨的任务。 而且可能非常容易出错。

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

 void Button_Clicked( object sender, EventArgs e ) { PerformSteps( ButtonClickCoRoutine ); } private IEnumerable 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 result, out T store ) { store = result; PerformSteps(); // continues iterating steps } private IEnumerable m_StepsToPerform; private void PerformSteps( IEnumerable 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(); } 

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

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

你应该看看并发和协调运行时 。 它使用迭代器来实现此目的。

另一方面,您还应该看看Parallel Extensions及其延续方法。 Parallel Extensions是.NET 4.0的一部分,而CCR需要单独的许可。 我建议你选择一个由吃饭,呼吸和睡觉的人写的框架。 你自己弄错了细节太容易了。

.NET的Reactive Extensions提供了一个更清晰的模型来处理它。

它们提供了扩展,使您可以以更清晰的方式编写针对异步事件的简单委托。 我建议调查它们,并使它们适应这种情况。

我没有看完你的全部内容。

他们在CCR机器人工作室中使用此策略,并且许多其他项目使用此策略。 另一种方法是使用LINQ,请参阅此博客以获取说明。 Reactive框架(Rx)有点沿着这些方向构建。

Luca在他的PDC演讲中提到,未来版本的C#/ VB可能会在语言中添加异步原语。

与此同时,如果您可以使用F#,那么这是一个成功的策略。 现在你可以用F#做什么,这样就可以把所有其他东西从水里吹走。

编辑

要引用我博客中的示例,假设您有一个WCF客户端,您想要调用几个方法。 同步版本可能写为

 // 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 } 

没有疯狂的回调,你可以使用像if-then-else这样的控制结构,而try-finally等等,写它几乎就像你编写直线代码一样,一切正常,但现在它是异步的。 获取给定的BeginFoo / EndFoo方法并使相应的F#异步方法在此模型中使用非常容易。

您可能还想考虑Jeffrey Richter的AsyncEnumerator,它是“power threading”库的一部分。 他与CCR团队合作开发CCR。 根据Jeffrey的说法,AsyncEnumerator比CCR更“轻量级”。 我个人用AsyncEnumerator而不是CCR玩过。

我没有在愤怒中使用它 – 到目前为止,我发现使用枚举器来实现协程的限制太痛苦了。 目前正在学习F#,因为除了其他事项的异步工作流程(如果我记得正确的名称),看起来他们是完全成熟的协程或’延续’(我忘记了正确的名称或术语之间的确切区别)。

无论如何,这里有一些链接:

http://www.wintellect.com/PowerThreading.aspx

AsyncEnumerator上的第9频道video

MSDN文章