2009年7月19日日曜日

WF備忘録1:進行途上のReceiveActivityにメッセージを送る

最近、.NET 3.5 のWFについて調べる機会があったので、備忘的に書いておく。

調べたことは、WCF サービスとして公開しているワークフローへ、メッセージを投げる方法。
以下、クライアント側でのワークフロー・インスタンスの識別子(以下インスタンスID)の指定のしかたについて、次のような順で説明する。
  1. 実験用ワークフローの作成
  2. インスタンスIDを指定しない場合の挙動
  3. 3.ワークフローIDを指定した場合の挙動
※参考とした記事は↓↓↓
Long running workflows and the ReceiveActivity

1.★実験用ワークフローの作成
はじめに、外部からのメッセージを受け付けるアクティビティ(Receiveアクティビティ)を二つ持つワークフローを、WCFサービスとして作成する。
  • ・「シーケンシャル ワークフロー サービス ライブラリ」を新規作成(プロジェクトの種類は「WCF」)
  • IWorkflow1インターフェイスのGetDataメソッドの宣言を以下のように変更
    string GetData();
  • Codeアクティビティを、"receiveActivity1"アクティビティにドロップする。
  • ハンドラを生成し、中身を以下のように記述
    returnValue = "step1";
  • receiveActivity1をコピーし、その直下に貼り付け。
  • codeActivity2のExecuteCodeハンドラの名前をcodeActivity1_ExecuteCodeからcodeActivity2_ExecuteCodeに変更。
  • codeActivity2_ExecuteCodeの中身は以下のように記述
    returnValue = "step2";
ここまでの作業で、[F5]を押せばWCFテスト環境が立ち上がり、IWorkflow1.GetData()が文字列"step1"を返す事を確認できる。

2.インスタンスIDを指定しない場合の挙動
次に、これを起動するクライアントを書いて、繰り返してメッセージを投げたときにどうなるかを見る。

  • コンソールアプリケーションプロジェクトを追加
  • 先のWFServiceLibrary1を「デバッグなしで実行」
  • WCFテストクライアントで、IWorkflow1サービスのアドレスをコピー
  • コンソールアプリのプロジェクトにて、コピーしたアドレスを指定して「サービス参照の追加」
  • クライアントプロキシを呼ぶコードをProgram.Main()に追加。using などは適当に追加する。
    static void Main(string[] args)
    {
    Workflow1Client proxy = new Workflow1Client();
    string result = proxy.GetData();
    Console.WriteLine(result);

    Console.ReadKey();
    }
以下のように実行して挙動を確認。
  1. まずWFServiceLibrary1サービスを、「デバッグ」で起動
  2. ConsoleApplication1のコンテキストメニューから、「デバッグ」→ 「新しいインスタンスを開始」
  3. コンソールに"step1"と表示されることを確認。
  4. 再び、手順2を実行し、再び"step1"が返される事を確認
やはり、2つ目のReceiveアクティビティが呼び出されない事がわかる。


3.インスタンスIDを指定した場合の挙動
上記の挙動は、次のように考えられる。
  • 一回目のGetDataの呼出しで新しいフローのインスタンスが生成され、1つ目のアクティビティのExecuteCodeハンドラにより"step1"が返される。
  • 1つ目のアクティビティを終えたフローは2つ目のアクティビティに進み、メッセージを受け取ったら文字列"step2"を返そうと待機する事になる。
  • ところが、クライアントからの二回目のGetData呼び出し時、ランタイムは、メッセージがどのワークフローインスタンスへのものか判別できないので、新しいインスタンスを生成し、最初のReceiveアクティビティにメッセージを送り、再び"step1"が返された。
そこで、クライアント側コードでは、最初に"step1"を返してきたのが、どのワークフローのインスタンスであったかを保持し、2回目の呼出で指定する必要がある。

まず、最初の呼出直後にワークフローIDを保持するコードは、以下のように書く。
  • 上で書いたProgram.Main()を以下のように変更(usingやアセンブリ参照は適当に追加)
    static void Main(string[] args)
    {
    //まずは普通にメッセージを投げる
    Workflow1Client proxy = new Workflow1Client();
    string result = proxy.GetData();
    Console.WriteLine(result);

    //インスタンスIDを取得して、コンソールに表示する
    string instanceId = null;
    IContextManager ctxMan = proxy.InnerChannel.GetProperty();
    Dictionary context = (Dictionary)ctxMan.GetContext();
    context.TryGetValue("instanceId", out instanceId);
    Console.WriteLine("instanceId:" + instanceId);

    Console.ReadKey();
    }
実行すると、"instanceId:"に続いて識別子っぽい文字列が表示されるのが確認できる。


次は、この表示されたワークフローIDを渡すコードだが、わかりやすくするために別のプロジェクトにする。
  • 新規コンソールアプリプロジェクトを追加して、Program.Main() を以下のように変更。(usingとアセンブリ参照も適当に追加)
    static void Main(string[] args)
    {
    Console.Write("input workflow id:");
    string workflowId = Console.ReadLine();

    Dictionary context = new Dictionary();
    context.Add("instanceId", workflowId);

    Workflow1Client proxy = new Workflow1Client();
    IContextManager ctxMan = proxy.InnerChannel.GetProperty();
    ctxMan.SetContext(context);

    string result = proxy.GetData();
    Console.WriteLine(result);

    Console.ReadKey();
    }
ここまでの作業で、動作確認の準備ができた。以下のように実施。
  • まずワークフローライブラリをデバッグで立ち上げる。
  • 最初のコンソールアプリを立ち上げる。"step1" の下にワークフローIDが表示される事を確認。"instanceId:"の右の部分をコピーする。
  • 2つ目のコンソールアプリを立ち上げる。"input workflow id:"と聞いてくるので、コピーしたワークフローIDを張り付けて、[Enter]押下。
  • "step2"と表示されて、ワークフローの2つ目のReceiveアクティビティにメッセージが届いたことが分かる。
以上で、インスタンスIDを指定することで、流れている途中のワークフローにメッセージを送ることができると分かった。今回は、あえて手作業でワークフローインスタンスIDをコピーしたり貼り付けたりする方針でやってみた。NUnitなどを使って、一発で確認できる方法でも良かったが、ロングランニングなワークフローを模した方法を採ってみた。実開発では、このインスタンスIDをデータベースに保持したりする事になるのだと思う。

0 件のコメント:

コメントを投稿