我正在编写重构 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 版本变得更加复杂(即使没有异常处理和前置/后置条件测试):
// 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 或其他任何地方找到一个。在此先感谢您的帮助。