[WF 4 (7)] アクティビティにおけるさまざまな実行管理

環境 : Visual Studio 2010 Beta 2 (.NET Framework 4)

WF 4

  1. そのアイデアとベースクラスの変更
  2. コードいらずのワークフロー (入門)
  3. ワークフローのコードと XAML の内部
  4. フローチャート (FlowCharts)
  5. アクティビティ (基礎)
  6. アクティビティコンテキスト (context) と変数 (Variable) の意義
  7. アクティビティにおけるさまざまな実行管理
  8. アクティビティのデリゲート
  9. アクティビティの非同期実行
  10. アクティビティデザイナー
  11. ブックマーク
  12. Workflow Extensions と 永続化、トラッキング
  13. デザイナーリホスティングとカスタムアプリケーション

こんにちは。

第 1 回で、「Activity には CodeActivity と NativeActivity があり」、「NativeActivity を使用すると実行管理を簡単におこなうことができる」 と説明しました。今日は、前回に続き、WF 4 のアクティビティ (activity) のコンテキスト (context) を使ったさらなる応用例として、この辺りのポイントをいくつか例示してご紹介しましょう。

これまで WF をさわったことがない方は、「へー、そうだったんだ」くらいに思ってもらえればOKですが、従来の WF では、真面目に “実用的なアクティビティ” を作成しようとすると、実は、かなり骨の折れる作業でした。例えば、従来であれば、ワークフローの実行をおこなうたびに、現在の実行状態を返り値などで返す (例えば、ActivityExecutionStatus.Executing, ActivityExecutionStatus.Closed などを返す) 必要があり、単に「Sequence アクティビティ (SequenceActivity) と同じアクティビティを実装したい」という場合でも、

  1. まず、最初の子アクティビティ終了時のイベントハンドラ (Activity.Closed イベント) を受け取るためのイベントの登録をおこなう
  2. 最初の子アクティビティを実行 (executeActivity メソッドを実行) して (子アクティビティが、実行用のキューに入ります)、ActivityExecutionStatus.Executing を返す
  3. この子アクティビティ終了後、上記のイベントハンドラが呼ばれるので、この中で、つぎのアクティビティを検索して、さらに上記 1 と同じことを繰り返す (以降、同様)
  4. すべての子アクティビティが完了したら、ActivityExecutionStatus.Closed を返す

といった処理になります。(興味がある方は 「カスタム複合アクティビティの作成」 などを参照してみてください。)

コードを想像していただくとわかりますが、処理があちこちに飛んで読みづらく、イベント処理のためのインタフェース (IActivityEventListener) とそのメソッドを実装するなど、読みづらいコードになっていたことでしょう。

WF 4 では、こうした「状態の管理」も自動化されています。上述した「Sequence アクティビティと同じアクティビティ」は、NativeActivity を使うと、以下のようになります。

public sealed class MySequenceActivity : NativeActivity
{
    public Collection<Activity> Activities = new Collection<Activity>();
    Variable<Int32> CurrentIndex = new Variable<Int32>();

    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        metadata.SetImplementationChildrenCollection(Activities);
        metadata.AddImplementationVariable(CurrentIndex);
        base.CacheMetadata(metadata);
    }

    protected override void Execute(NativeActivityContext context)
    {
        MyExecute(context, null);
    }

    // コールバック用に、別メソッドにしました . . .
    private void MyExecute(NativeActivityContext context, object sender)
    {
        int currentIndex = this.CurrentIndex.Get(context);
        if (currentIndex == Activities.Count)
            return;
        Activity next = Activities[currentIndex];
        context.ScheduleActivity(next, new CompletionCallback(MyExecute));
        this.CurrentIndex.Set(context, currentIndex + 1);
    }
}

static void Main(string[] args)
{
    WorkflowInvoker.Invoke(new MySequenceActivity
    {
        Activities =
        {
            new WriteLine { Text = "Hello !" },
            new WriteLine { Text = "ようこそ !" },
            new WriteLine { Text = "バイバイ" }
        }
    });

    Console.ReadLine();
}

この実行結果は、”Hello !”、”ようこそ !”、”バイバイ” が順番に表示されます。

さらに、子アクティビティの制御も楽勝です。
例えば、実行の途中に子アクティビティの実行をキャンセルしたい場合、今回から、NativeActivityContext に CancelChildren というメソッドがあります。

以下は、並列に処理をおこない、どれか 2 つの子アクティビティが完了したら、残りはすべてキャンセルしてしまうというサンプルコードになります。

public sealed class ParallelCancelActivity : NativeActivity
{
    public Collection<Activity> Activities = new Collection<Activity>();
    Variable<Int32> CompletedNum = new Variable<Int32>();

    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        metadata.SetImplementationChildrenCollection(Activities);
        metadata.AddImplementationVariable(CompletedNum);
        base.CacheMetadata(metadata);
    }

    protected override void Execute(NativeActivityContext context)
    {
        CompletedNum.Set(context, 0);
        Activities.ToList<Activity>().ForEach(activity => context.ScheduleActivity(activity, new CompletionCallback(OnChildComplete)));
    }

    void OnChildComplete(NativeActivityContext context, object sender)
    {
        int completedNum = CompletedNum.Get(context);
        CompletedNum.Set(context, completedNum + 1);
        // 2 つ完了したら、残りはキャンセル !
        if (CompletedNum.Get(context) == 2)
            context.CancelChildren();
    }
}

また、HandleFault メソッドを使うと、自分でエラー時の処理をおこなうように指定できます。

以下のサンプルコードでは、c:tmp フォルダが作成されるまで 100 回リトライをおこない (それぞれ 1 秒間ずつ待機します)、c:tmp フォルダが作成されると、処理はそこで成功して終了します。(実行時は、F5 のデバッグ実行ではなく、Ctrl + F5 のデバッグなしで実行をおこなうようにしてください。)

// 子アクティビティとして、下記の TestActivity を実行するアクティビティ
public sealed class FaultHandleActivity : NativeActivity
{
    public Collection<Activity> Activities = new Collection<Activity>();
    private Variable<Int32> RetryNum = new Variable<Int32>();

    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        metadata.SetImplementationChildrenCollection(Activities);
        metadata.AddImplementationVariable(RetryNum);
        base.CacheMetadata(metadata);
    }

    protected override void Execute(NativeActivityContext context)
    {
        RetryNum.Set(context, 0);
        Activities.ToList().ForEach(activity =>
            context.ScheduleActivity(activity,
            ((c, i) => Console.WriteLine("Succeed !")),
            new FaultCallback(OnFault)));
    }

    void OnFault(NativeActivityFaultContext context, Exception ex,
        ActivityInstance instance)
    {
        Console.WriteLine("Fault ! {0}", ex.GetType());

        int retryNum = RetryNum.Get(context);
        if (retryNum < 100)
        {
            context.HandleFault();  // Exception を自分で処理
            System.Threading.Thread.Sleep(1000);  // 1 秒待つ !
            RetryNum.Set(context, retryNum + 1);
            context.ScheduleActivity(instance.Activity,
                null,
                new FaultCallback(OnFault));
        }
    }
}

// c:tmptest.txt に文字列を書き込むアクティビティ
public sealed class TestActivity : CodeActivity
{
    protected override void Execute(CodeActivityContext context)
    {
        TextWriter writer = new StreamWriter(@"c:tmptest.txt", true);
        writer.WriteLine("Hello !");
        writer.Close();
    }
}

. . .
static void Main(string[] args)
{
    WorkflowInvoker.Invoke(new FaultHandleActivity
    {
        Activities =
        {
            new TestActivity()
        }
    });

    Console.ReadLine();
}

ここではいくつかのサンプルを使って確認しましたが、NativeActivity で使用される NativeActivityContext では、既定でさまざまなメソッドを提供しており、煩雑な管理をすべて自動化してくれるようになっているため、複雑な処理をおこなうカスタムアクティビティも非常に簡単に作成できるようになっています。

仕事が山積しているため今日はこの辺で終了し、「WF 4 が非常に細かな実行管理に対処している」という例として、次回、また別の切り口で掘り下げていきたいと思います。。。

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s