Reactive Extensionsを使用して複数のサービス非同期コールを連続して呼べるか試してみた。

Silverlightのサービスコールは非同期のみサポートとなっています。そのため、Silverlightアプリでサービスコールを記述するのはちょっと大変です。
そこで、Reactive Extensionsを使用して、非同期通信処理を連続して書けるか試してみます。

アジェンダ

  • Reactive Extensionsとは
  • Reactive Extensionsのインストー
  • プロジェクトの作成
  • テスト用サービスの作成
  • サービスコールを1つ呼ぶ
  • サービスコールを複数連続で呼ぶ
  • 課題
  • まとめ

Reactive Extensionsとは

Reactive Extensionsを説明するのは、正直私の手にはあまりまくりな代物です。下記のサイトなどが参考になると思います。

Reactive Extensionsのインストー

最初にSilverlight用のReactive Extensionsをサイトからダウンロードしインストールしましょう。

プロジェクトの作成

通常のSilvrlightアプリケーションと同様にプロジェクトを作成します。そして、Silvrlightアプリプロジェクトに以下のアセンブリの参照を追加します。

  • System.Interactive.dll
  • System.Reactive.dll
  • System.CoreEx.dll
  • System.Observable.dll

テスト用サービスの作成

テスト用に使うWCFサービスを作成します。

[ServiceContract]
public interface IService1
{
    [OperationContract]
    string GetA(string keyA);

    [OperationContract]
    string GetB(string keyB);

    [OperationContract]
    string GetC(string keyC);
}

public class Service1 : IService1
{
    public string GetA(string keyA)
    {
        System.Diagnostics.Debug.WriteLine("Server.GetA");
        return keyA+" : ValueA";
    }

    public string GetB(string keyB)
    {
        // 重い処理を実行
        Thread.Sleep(5000);

        System.Diagnostics.Debug.WriteLine("Server.GetB");
        return keyB + " : ValueB";
    }

    public string GetC(string keyC)
    {
        // 重い処理を実行
        Thread.Sleep(5000);

        System.Diagnostics.Debug.WriteLine("Server.GetC");
        return keyC + " : ValueC";
    }
}

引数で受け取った文字列に文字列を連結して返すだけの単純なサービスを3つ用意しました。

サービスコールを1つ呼ぶ

では、実際にSilvrlightアプリからReactive Extensionsを使用してサービスコールをします。最初は、サービスを1つのみコールします。以下のサイトを参考にしました。

// サービスクライアントの生成
Service1Client client = new Service1Client();

// サービスコンプリートイベント監視用Observableインスタンスを定義
IObservable<IEvent<GetACompletedEventArgs>> getACompleted
    = Observable.FromEvent<GetACompletedEventArgs>(client, "GetACompleted");

// サービスコンプリートをキャッチし、処理を行う。
getACompleted.Subscribe((IEvent<GetACompletedEventArgs> e) =>
{
    System.Diagnostics.Debug.WriteLine("サービスコール完了 = " + e.EventArgs.Result);
});

client.GetAAsync("test");

通常のサービスコールとほとんど変わらないコードになります。ポイントは、Observable.FromEventメソッドで作ったIObservable型のインスタンスのSubscribeメソッドを介してサービスからの戻り値を受け取るところです。
上記のコードを実行すると、出力結果はこうなります。

Server.GetA
サービスコール完了 = test : ValueA

サービスコールを複数連続で呼ぶ

それでは、複数のサービスコールをチェーン状にしたコードを書いてみます。チェーン上にするために、SubscribeメソッドではなくSelectManyメソッドを使用します。SelectManyメソッドを使うと、Subscribeメソッドのようにサービスコンプリートをキャッチでき、しかも戻り値で他のObservableインスタンスを指定することができます。

// サービスクライアントの生成
Service1Client client = new Service1Client();

// サービスコンプリートイベント監視用Observableインスタンスを定義
IObservable<IEvent<GetACompletedEventArgs>> getACompleted
    = Observable.FromEvent<GetACompletedEventArgs>(client, "GetACompleted");

IObservable<IEvent<GetBCompletedEventArgs>> getBCompleted
    = Observable.FromEvent<GetBCompletedEventArgs>(client, "GetBCompleted");

IObservable<IEvent<GetCCompletedEventArgs>> getCCompleted
    = Observable.FromEvent<GetCCompletedEventArgs>(client, "GetCCompleted");

// サービスコールチェーンを定義
getACompleted.SelectMany((IEvent<GetACompletedEventArgs> e) =>
{
    System.Diagnostics.Debug.WriteLine("Client1. call GetBAsync");
    string keyB = e.EventArgs.Result;
    client.GetBAsync(keyB);

    System.Diagnostics.Debug.WriteLine("Client2. return getBCompleted");
    return getBCompleted;
})
.SelectMany((IEvent<GetBCompletedEventArgs> e) =>
{
    // getBCompletedで監視していたイベントの戻り値をSselectManyで取得できる。
    System.Diagnostics.Debug.WriteLine("Client3. call GetCAsync");
    string keyC = e.EventArgs.Result;
    client.GetCAsync(keyC);
    System.Diagnostics.Debug.WriteLine("Client4. return getCCompleted");
    return getCCompleted;
})
.Subscribe((IEvent<GetCCompletedEventArgs> e) =>
{
    string result = e.EventArgs.Result;
    System.Diagnostics.Debug.WriteLine("Client5. 非同期サービスコールチェーン終了 = " + result);
});

// サービスコールチェーンを開始する。
client.GetAAsync("さーびすこーるちぇーん開始");

実行結果を見てみると、SelectManyまたはSubscribeメソッドに渡した関数に値がプッシュされるのは、サービスから戻り値が帰ってきてからになっています。ということで、非同期サービスコールのチェーンは順番通りに動いているようです。

Server.GetA
Client1. call GetBAsync
Client2. return getBCompleted
Server.GetB
Client3. call GetCAsync
Client4. return getCCompleted
Server.GetC
Client5. 非同期サービスコールチェーン終了 = さーびすこーるちぇーん開始 : ValueA : ValueB : ValueC

課題

一応、非同期サービスコールを連続して呼ぶことができました。しかし今回の方法は課題があります。

  • チェーンの始動ポイントがチェーンの後ろにあるのが嫌。
  • サービスコールでエラーが発生した場合、チェーンのキャンセル・エラー内容をUIに表示と言ったエラー処理をどのように行うか。
  • 2個以上前に呼んだサービスの引数を使ってサービスコールをしたい場合、どのように対処するか。
  • いちいち、getACompletedなどを定義するのが面倒くさい。
  • サービスからの戻り値によって次に呼ぶサービスを分岐させたい場合、どのように対処するか。

まとめ

Reactive Extensionsを使用して、非同期通信処理を連続して書けるかを試してみました。結果一応書けましたが、課題が残ってしまいました。良い解決方法が見つかったら、またチャレンジしてみたいです。