在 WF 中创建异步活动

AsyncCodeActivity 提供一个可供活动作者使用的基类,该基类允许派生的活动实现异步执行逻辑。 这对如下自定义活动非常有用:必须执行异步工作,而不会保持工作流计划程序线程并阻止可以并行运行的所有活动。 本主题概述了如何使用 AsyncCodeActivity 创建自定义异步活动。

使用 AsyncCodeActivity

System.Activities 为自定义活动作者提供了不同基类,以满足不同的活动创作需求。 每一种基类都采用特定语义,并为工作流作者(和活动运行时)提供相应协定。 基于 AsyncCodeActivity 的活动是指异步执行工作(相对于计划程序线程)且以托管代码表示其执行逻辑的活动。 由于将异步执行,因此 AsyncCodeActivity 可能会在执行时引入一个空闲点。 鉴于异步工作灵活多变的性质,AsyncCodeActivity 始终创建一个非持久性块来持久执行活动。 这可以防止工作流运行时在异步工作中持久化工作流实例,还可以防止工作流实例在执行异步代码时进行卸载。

AsyncCodeActivity 方法

AsyncCodeActivity 派生的活动可以通过用自定义代码重写 BeginExecuteEndExecute 方法来创建异步执行逻辑。 当运行时调用这些方法时,会向这些方法传递一个 AsyncCodeActivityContext。 通过 AsyncCodeActivityContextBeginExecute/ EndExecuteUserState,活动作者可以通过 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> 的提升 ResultGetRandom 方法返回的值由 EndExecute 重写提取和返回,并将此值设置为 Result 值。 未返回结果的异步活动应派生自 AsyncCodeActivity。 在下面的示例中,定义了一个派生自 DisplayRandomAsyncCodeActivity 活动。 此活动与 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));
    }
}

请注意,由于没有返回值,DisplayRandom 将使用 Action 而非 Func<T,TResult> 来调用其委托,并且该委托不返回任何值。

AsyncCodeActivity 还提供 Cancel 重写。 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 属性。 这是用于在 BeginExecuteEndExecute 之间共享状态的正确方法。 使用派生类(在这个例子中为 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 派生的自定义活动提供了以异步方式执行与工作流线程相关的工作的方法,但不提供安排子活动或操作的功能。 但是,可以通过组合方式将异步行为纳入子活动的安排。 可以创建一个异步活动,然后将其与 ActivityNativeActivity 派生活动组合在一起,以便提供异步行为以及子活动或操作的安排。 例如,可以创建一个活动,该活动派生自 Activity,并且作为其实现令 Sequence 包含异步活动以及实现该活动的逻辑的其他活动。 有关使用 ActivityNativeActivity 编写活动的更多示例,请参阅如何:创建活动活动创作选项

另请参阅