16

私は、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つを見つけることができませんでした。助けてくれてありがとう。

4

4 に答える 4

11

Concurrency and Coordination Runtimeを必ず確認してください。まさにこの目的のためにイテレータを使用します。

一方、並列拡張とその継続へのアプローチも検討する必要があります。Parallel Extensions は .NET 4.0 の一部ですが、CCR には別のライセンスが必要です。ただし、このようなものを食べ、呼吸し、眠る人々によって書かれたフレームワークを使用することお勧めします。自分で詳細を間違えるのはあまりにも簡単です。

于 2009-12-14T21:17:33.923 に答える
4

Reactive Extensions for .NETは、これを処理するためのよりクリーンなモデルを提供します。

これらは、非同期イベントに対する単純なデリゲートをよりクリーンな方法で記述できるようにする拡張機能を提供します。それらを調べて、この状況に適応させることをお勧めします。

于 2009-12-14T21:18:02.207 に答える
1

私はあなたのすべてを読んでいませんでした。

彼らは 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、while、try-finally などの制御構造を使用でき、直線的なコードを書くのとほとんど同じように記述でき、すべてが機能しますが、今は非同期です。特定の BeginFoo/EndFoo ペアのメソッドを取得して、このモデルで使用する対応する F# 非同期メソッドを作成するのは非常に簡単です。

于 2009-12-14T21:18:14.997 に答える
0

また、Jeffrey Richter の「パワー スレッディング」ライブラリの一部である AsyncEnumerator を検討することもできます。彼は CCR チームと協力して CCR を開発しました。AsyncEnumerator は、Jeffrey によれば、CCR よりも「軽量」です。個人的に私は AsyncEnumerator で遊んだことがありますが、CCR では遊んでいません。

私は怒ってそれを使用したことはありません - これまでのところ、列挙子を使用してコルーチンを実装することの制限があまりにも苦痛であることがわかりました. 現在 F# を学習しているのは、特に非同期ワークフロー (名前が正しく覚えていれば) が本格的なコルーチンまたは「継続」のように見えるためです (正しい名前または用語間の正確な違いを忘れてしまいました)。

とにかく、ここにいくつかのリンクがあります:

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

AsyncEnumerator のチャネル 9 ビデオ

MSDN の記事

于 2009-12-15T00:53:11.370 に答える