私は、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();
}
上記のコードは、例外処理、nullityチェック、および本番コードで必要となるその他のプラクティスを省略しているという点で、明らかに単純化されています。それでも、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、またはその他の場所で1つを見つけることができませんでした。助けてくれてありがとう。