WF 内での非同期アクティビティの作成

AsyncCodeActivity は使用する基本クラスをアクティビティ作成者に提供します。その結果、派生アクティビティが非同期実行ロジックを実装できるようになります。 これは、ワークフローのスケジューラ スレッドを保持したり、並行して実行される可能性があるすべてのアクティビティをブロックしたりすることなく、非同期作業を実行する必要があるカスタム アクティビティに役立ちます。 ここでは、AsyncCodeActivity を使用してカスタムの非同期アクティビティを作成する方法の概要を説明します。

AsyncCodeActivity の使用

System.Activities は、カスタム アクティビティ作成者に、アクティビティの作成要件に応じてさまざまな基本クラスを提供します。 それぞれが特定のセマンティックを持ち、ワークフロー作成者 (およびアクティビティ ランタイム) に対応するコントラクトを提供します。 AsyncCodeActivity ベースのアクティビティは、スケジューラ スレッド、およびマネージド コードで表される実行ロジックに関連して、非同期に作業を実行するアクティビティです。 非同期作業になると、AsyncCodeActivity によって実行中にアイドル ポイントが発生する可能性があります。 非同期作業には揮発的な性質があるため、AsyncCodeActivity によりアクティビティの実行期間に対して常に非永続的なブロックが作成されます。 こうすることで、ワークフロー ランタイムによって、非同期作業中にワークフロー インスタンスが永続化されることを回避できます。また、非同期コードの実行中にワークフロー インスタンスがアンロードされることを回避できます。

AsyncCodeActivity メソッド

AsyncCodeActivity から派生するアクティビティでは、BeginExecute メソッドと EndExecute メソッドをカスタム コードでオーバーライドすることで、非同期実行ロジックを作成できます。 ランタイムによって呼び出されるとき、これらのメソッドには AsyncCodeActivityContext が渡されます。 AsyncCodeActivityContext を使用すると、アクティビティの作成者は BeginExecute/ EndExecute 間で共有した状態をコンテキストの UserState プロパティに指定できます。 次の例では、GenerateRandom アクティビティによって非同期に乱数を生成します。

public sealed class GenerateRandom : AsyncCodeActivity<int>
{
    static Random r = new Random();

    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
    {
        // Create a delegate that references the method that implements
        // the asynchronous work. Assign the delegate to the UserState,
        // invoke the delegate, and return the resulting IAsyncResult.
        Func<int> GetRandomDelegate = new Func<int>(GetRandom);
        context.UserState = GetRandomDelegate;
        return GetRandomDelegate.BeginInvoke(callback, state);
    }

    protected override int EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
    {
        // Get the delegate from the UserState and call EndInvoke
        Func<int> GetRandomDelegate = (Func<int>)context.UserState;
        return (int)GetRandomDelegate.EndInvoke(result);
    }

    int GetRandom()
    {
        // This activity simulates taking a few moments
        // to generate the random number. This code runs
        // asynchronously with respect to the workflow thread.
        Thread.Sleep(5000);

        return r.Next(1, 101);
    }
}

前の例のアクティビティは、AsyncCodeActivity<TResult> から派生し、OutArgument<int> という昇格した Result を備えています。 GetRandom メソッドが返す値は、EndExecute のオーバーライドによって抽出され、返されます。また、この値は Result 値として設定されます。 結果を返さない非同期アクティビティは、AsyncCodeActivity から派生する必要があります。 次の例では、DisplayRandom から派生する AsyncCodeActivity アクティビティが定義されます。 このアクティビティは GetRandom アクティビティに似ていますが、結果を返す代わりにコンソールにメッセージを表示します。

public sealed class DisplayRandom : AsyncCodeActivity
{
    static Random r = new Random();

    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
    {
        // Create a delegate that references the method that implements
        // the asynchronous work. Assign the delegate to the UserState,
        // invoke the delegate, and return the resulting IAsyncResult.
        Action GetRandomDelegate = new Action(GetRandom);
        context.UserState = GetRandomDelegate;
        return GetRandomDelegate.BeginInvoke(callback, state);
    }

    protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
    {
        // Get the delegate from the UserState and call EndInvoke
        Action GetRandomDelegate = (Action)context.UserState;
        GetRandomDelegate.EndInvoke(result);
    }

    void GetRandom()
    {
        // This activity simulates taking a few moments
        // to generate the random number. This code runs
        // asynchronously with respect to the workflow thread.
        Thread.Sleep(5000);

        Console.WriteLine("Random Number: {0}", r.Next(1, 101));
    }
}

戻り値がないため、DisplayRandomAction の代わりに Func<T,TResult> を使用してデリゲートを呼び出し、デリゲートは値を返さないことに注意してください。

また、AsyncCodeActivityCancel のオーバーライドも提供します。 BeginExecuteEndExecute は必須のオーバーライドですが、Cancel はオプションで、アクティビティを取り消したり中止したりするときに未処理の非同期状態を消去できるようにオーバーライドすることが可能です。 消去が可能で AsyncCodeActivity.ExecutingActivityInstance.IsCancellationRequestedtrue の場合、アクティビティでは MarkCanceled を呼び出します。 このメソッドからスローされるすべての例外は、ワークフロー インスタンスにとって致命的なものです。

protected override void Cancel(AsyncCodeActivityContext context)
{
    // Implement any cleanup as a result of the asynchronous work
    // being canceled, and then call MarkCanceled.
    if (context.IsCancellationRequested)
    {
        context.MarkCanceled();
    }
}

クラスでの非同期メソッドの呼び出し

.NET Framework 内のクラスの多くには非同期機能が備わっており、AsyncCodeActivity ベースのアクティビティを使用してこの機能を非同期に呼び出すことができます。 次の例では、FileStream クラスを使用して非同期にファイルを作成するアクティビティが作成されます。

public sealed class FileWriter : AsyncCodeActivity
{
    public FileWriter()
        : base()
    {
    }

    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
    {
        string tempFileName = Path.GetTempFileName();
        Console.WriteLine("Writing to file: " + tempFileName);

        FileStream file = File.Open(tempFileName, FileMode.Create);

        context.UserState = file;

        byte[] bytes = UnicodeEncoding.Unicode.GetBytes("123456789");
        return file.BeginWrite(bytes, 0, bytes.Length, callback, state);
    }

    protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
    {
        FileStream file = (FileStream)context.UserState;

        try
        {
            file.EndWrite(result);
            file.Flush();
        }
        finally
        {
            file.Close();
        }
    }
}

BeginExecute メソッドと EndExecute メソッドの間での状態の共有

前の例では、FileStream 内で作成した BeginExecute オブジェクトに EndExecute 内でアクセスしています。 このことは、file 変数を AsyncCodeActivityContext.UserState 内で BeginExecute のプロパティに渡すことで可能になっています。 これは BeginExecute および EndExecute の間で状態を共有するための正しい方法です。 派生クラス内 (この場合は FileWriter) のメンバー変数を使用して BeginExecuteEndExecute の間で状態を共有するのは正しい方法ではありません。そのようにすると、アクティビティのオブジェクトが複数のアクティビティ インスタンスから参照される可能性があります。 メンバー変数を使用して状態を共有すると、いずれかの ActivityInstance の値が上書きされたり、別の ActivityInstance の値が使用されたりする場合があります。

引数値へのアクセス

AsyncCodeActivity の環境は、アクティビティに定義されている引数から構成されます。 これらの引数には、AsyncCodeActivityContext パラメーターを使用して BeginExecute/EndExecute オーバーライドからアクセスできます。 デリゲート内で引数にアクセスすることはできませんが、デリゲートのパラメーターを使用すると、引数値や任意の他の必要なデータをデリゲートに渡すことができます。 次の例では、Max 引数から包含的上限を取得する乱数生成アクティビティを定義します。 デリゲートを呼び出すと、引数の値は非同期コードに渡されます。

public sealed class GenerateRandomMax : AsyncCodeActivity<int>
{
    public InArgument<int> Max { get; set; }

    static Random r = new Random();

    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
    {
        // Create a delegate that references the method that implements
        // the asynchronous work. Assign the delegate to the UserState,
        // invoke the delegate, and return the resulting IAsyncResult.
        Func<int, int> GetRandomDelegate = new Func<int, int>(GetRandom);
        context.UserState = GetRandomDelegate;
        return GetRandomDelegate.BeginInvoke(Max.Get(context), callback, state);
    }

    protected override int EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
    {
        // Get the delegate from the UserState and call EndInvoke
        Func<int, int> GetRandomDelegate = (Func<int, int>)context.UserState;
        return (int)GetRandomDelegate.EndInvoke(result);
    }

    int GetRandom(int max)
    {
        // This activity simulates taking a few moments
        // to generate the random number. This code runs
        // asynchronously with respect to the workflow thread.
        Thread.Sleep(5000);

        return r.Next(1, max + 1);
    }
}

AsyncCodeActivity を使用した、アクションまたは子アクティビティのスケジュール設定

AsyncCodeActivity 派生カスタム アクティビティはワークフローのスレッドに関する作業を非同期に実行するメソッドを提供しますが、子アクティビティやアクションをスケジュールする機能はありません。 ただし、非同期動作は、コンポジションを介して子アクティビティのスケジュール設定と組み合わせることができます。 非同期アクティビティを作成してから、Activity または NativeActivity の派生アクティビティと共に構成すると、子アクティビティまたはアクションの非同期動作とスケジュール機能を提供できます。 たとえば、アクティビティを Activity から派生するように作成し、その実装時に Sequence に非同期アクティビティと共にアクティビティのロジックを実装する他のアクティビティを含めることができます。 ActivityNativeActivity を使用してアクティビティを構成するその他の例については、「方法: アクティビティを作成する」とアクティビティ作成オプションに関するページを参照してください。

関連項目