为工作流中的取消行为进行建模

可以在工作流内部通过一个 Parallel 活动(此活动在其 CompletionCondition 的计算结果为 true 时将取消不完整的分支)取消活动,也可以从工作流外部取消活动(如果主机调用 Cancel)。 若要提供取消处理,工作流作者可以使用 CancellationScope 活动和 CompensableActivity 活动,也可以创建提供取消逻辑的自定义活动。 本主题概述了工作流中的取消。

取消、补偿和事务

利用事务,应用程序可以在事务进程的任何部分中出现错误时中止(回滚)事务内执行的所有更改。 但并非所有可能需要取消或撤销的工作都适用于事务,如长时间运行的工作或未涉及事务性资源的工作。 如果工作流中发生后续失败,补偿将提供用于撤销之前完成的非事务性工作的模型。 取消将为工作流作者和活动作者提供用于处理未完成的非事务性工作的模型。 如果某个活动的执行过程尚未完成就被取消,则将调用此活动的取消逻辑(如果可用)。

注意

若要详细了解事务和补偿,请参阅事务补偿

使用 CancellationScope

CancellationScope 活动包含两个部分,这两个部分可包含的子活动为 BodyCancellationHandlerBody 用于放置构成活动逻辑的活动,CancellationHandler 用于放置提供活动的取消逻辑的活动。 只能在某个活动尚未完成时取消此活动。 对于 CancellationScope 活动,完成是指完成 Body 中的活动。 如果安排了取消请求且 Body 中的活动尚未完成,则将 CancellationScope 标记为 Canceled,并执行 CancellationHandler 活动。

从主机取消工作流

主机可通过调用正在承载某个工作流的 Cancel 实例的 WorkflowApplication 方法来取消该工作流。 下面的示例创建一个具有 CancellationScope 的工作流。 调用该工作流后,主机将调用 Cancel。 停止工作流的主执行,并调用 CancellationHandlerCancellationScope,然后完成工作流,其状态为 Canceled

Activity wf = new CancellationScope
{
    Body = new Sequence
    {
        Activities =
        {
            new WriteLine
            {
                Text = "Starting the workflow."
            },
            new Delay
            {
                Duration = TimeSpan.FromSeconds(5)
            },
            new WriteLine
            {
                Text = "Ending the workflow."
            }
        }
    },
    CancellationHandler = new WriteLine
    {
        Text = "CancellationHandler invoked."
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Subscribe to any desired workflow lifecycle events.
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

Thread.Sleep(TimeSpan.FromSeconds(1));

wfApp.Cancel();

调用该工作流时,会将以下输出显示到控制台。

启动工作流。
CancellationHandler invoked.Workflow b30ebb30-df46-4d90-a211-e31c38d8db3c Canceled.

注意

在取消 CancellationScope 活动并调用 CancellationHandler 时,工作流作者应负责确定活动在完全取消之前的取消进度,以便提供适当的取消逻辑。 CancellationHandler 不提供有关活动的取消进度的任何信息。

如果某个未经处理的异常从工作流的根冒出,且 OnUnhandledException 处理程序返回 Cancel,则也可以从主机取消该工作流。 在此示例中,工作流将启动并引发 ApplicationException。 由于工作流未处理此异常,因此将调用 OnUnhandledException 处理程序。 该处理程序指示运行时取消工作流,并调用当前正在执行的 CancellationHandler 活动的 CancellationScope

Activity wf = new CancellationScope
{
    Body = new Sequence
    {
        Activities =
        {
            new WriteLine
            {
                Text = "Starting the workflow."
            },
            new Throw
            {
                 Exception = new InArgument<Exception>((env) =>
                     new ApplicationException("An ApplicationException was thrown."))
            },
            new WriteLine
            {
                Text = "Ending the workflow."
            }
        }
    },
    CancellationHandler = new WriteLine
    {
        Text = "CancellationHandler invoked."
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Subscribe to any desired workflow lifecycle events.
wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
    // Display the unhandled exception.
    Console.WriteLine("OnUnhandledException in Workflow {0}\n{1}",
        e.InstanceId, e.UnhandledException.Message);

    // Instruct the runtime to cancel the workflow.
    return UnhandledExceptionAction.Cancel;
};

wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

调用该工作流时,会将以下输出显示到控制台。

启动工作流。
工作流 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 中的 OnUnhandledException引发 ApplicationException。已调用 CancellationHandler。工作流 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 已取消。

从工作流内部取消活动

一个活动也可由其父级取消。 例如,如果 Parallel 活动具有多个正在执行的分支,且其 CompletionCondition 的计算结果为 true,则将取消其不完整的分支。 此示例创建一个具有两个分支的 Parallel 活动。 由于该活动的 CompletionCondition 设置为 true,因此只要完成其任一分支即完成 Parallel。 在此示例中,完成了分支 2,因此将取消分支 1。

Activity wf = new Parallel
{
    CompletionCondition = true,
    Branches =
    {
        new CancellationScope
        {
            Body = new Sequence
            {
                Activities =
                {
                    new WriteLine
                    {
                        Text = "Branch 1 starting."
                    },
                    new Delay
                    {
                         Duration = TimeSpan.FromSeconds(2)
                    },
                    new WriteLine
                    {
                        Text = "Branch 1 complete."
                    }
                }
            },
            CancellationHandler = new WriteLine
            {
                Text = "Branch 1 canceled."
            }
        },
        new WriteLine
        {
            Text = "Branch 2 complete."
        }
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

调用该工作流时,会将以下输出显示到控制台。

分支 1 启动。
分支 2 完成。分支 1 已取消。工作流 e0685e24-18ef-4a47-acf3-5c638732f3be 已完成。如果一个异常从活动的根冒出,但在工作流中的较高级别处理该异常,则也可以取消活动。 在此示例中,工作流的主逻辑由一个 Sequence 活动组成。 Sequence 将指定为 Body 活动所包含的 CancellationScope 活动的 TryCatchSequence 的正文中将引发一个异常,此异常由父 TryCatch 活动处理,并且将取消 Sequence

Activity wf = new TryCatch
{
    Try = new CancellationScope
    {
        Body = new Sequence
        {
            Activities =
            {
                new WriteLine
                {
                    Text = "Sequence starting."
                },
                new Throw
                {
                     Exception = new InArgument<Exception>((env) =>
                         new ApplicationException("An ApplicationException was thrown."))
                },
                new WriteLine
                {
                    Text = "Sequence complete."
                }
            }
        },
        CancellationHandler = new WriteLine
        {
            Text = "Sequence canceled."
        }
    },
    Catches =
    {
        new Catch<ApplicationException>
        {
            Action = new ActivityAction<ApplicationException>
            {
                Handler  = new WriteLine
                {
                    Text = "Exception caught."
                }
            }
        }
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

调用该工作流时,会将以下输出显示到控制台。

序列启动。
序列已取消。捕获异常。工作流 e3c18939-121e-4c43-af1c-ba1ce977ce55 已完成。

从 CancellationHandler 引发异常

对于工作流而言,从 CancellationHandlerCancellationScope 引发的任何异常都是严重异常。 如果存在 CancellationHandler 无法捕获的异常,请使用 TryCatch 中的 CancellationHandler 来捕获并处理这些异常。

使用 CompensableActivity 进行的取消

CancellationScope 活动一样,CompensableActivity 也具有一个 CancellationHandler。 如果取消 CompensableActivity,则将调用其 CancellationHandler 中的任何活动。 这对于撤销部分完成的可补偿工作很有用。 有关如何使用 CompensableActivity 进行补偿和取消的信息,请参阅补偿

使用自定义活动进行的取消

自定义活动作者可按照多种不同的方式实现其自定义活动中的取消逻辑。 派生自 Activity 的自定义活动可实现取消逻辑,方法是将一个 CancellationScope 或其他包含取消逻辑的自定义活动置于活动正文中。 AsyncCodeActivityNativeActivity 派生的活动可重写其各自的 Cancel 方法,并在其中提供取消逻辑。 CodeActivity 派生的活动不提供任何取消设置,原因是当运行时调用 Execute 方法时,将通过单次执行来执行其所有工作。 如果尚未调用执行方法,且取消了基于 CodeActivity 的活动,则该活动将关闭,其状态为 Canceled,并且不调用 Execute 方法。

使用 NativeActivity 进行的取消

NativeActivity 派生的活动可重写 Cancel 方法以提供自定义取消逻辑。 如果未重写此方法,则将应用默认工作流取消逻辑。 默认取消是适用于未重写 Cancel 方法或其 Cancel 方法调用了基 NativeActivityCancel  方法的 NativeActivity 的过程。 当取消某个活动时,运行时将标记此活动以进行取消,并自动处理特定清理。 如果此活动仅具有未处理的书签,则移除这些书签,并将此活动标记为 Canceled。 然后将取消已取消活动的任何未处理的子活动。 尝试安排其他子活动将导致尝试被忽略,且此活动将标记为 Canceled。 如果任何未处理的子活动在完成时处于 CanceledFaulted 状态,则此活动将标记为 Canceled。 请注意,可以忽略取消请求。 如果某个活动不具有任何未处理的书签或正在执行的子活动,并且在添加取消标记后不安排任何其他工作项,则将成功完成此活动。 虽然此默认取消可满足多种方案的需求,但如果需要其他取消逻辑,请使用内置取消活动或自定义活动。

在下面的示例中,将定义基于 Cancel 的自定义 NativeActivity 活动的 ParallelForEach 重写。 在取消此活动时,该重写将处理此活动的取消逻辑。 此示例是非泛型 ParallelForEach 示例的一部分。

protected override void Cancel(NativeActivityContext context)  
{  
    // If we do not have a completion condition then we can just  
    // use default logic.  
    if (this.CompletionCondition == null)  
    {  
        base.Cancel(context);  
    }  
    else  
    {  
        context.CancelChildren();  
    }  
}  

NativeActivity 派生的活动可通过检查 IsCancellationRequested 属性来确定是否已请求取消,并可通过调用 MarkCanceled 方法将自身标记为已取消。 调用 MarkCanceled 不会立刻完成此活动。 通常,运行时将在没有未处理的工作时完成活动,但如果调用 MarkCanceled,则最终状态将为 Canceled 而不是 Closed

使用 AsyncCodeActivity 进行的取消

基于 AsyncCodeActivity 的活动也可以通过重写 Cancel 方法来提供自定义取消逻辑。 如果未重写此方法,则在取消活动的情况下不执行任何取消处理。 在下面的示例中,将定义基于 Cancel 的自定义 AsyncCodeActivity 活动的 ExecutePowerShell 重写。 在取消此活动时,它将执行所需的取消行为。

// Called by the runtime to cancel the execution of this asynchronous activity.
protected override void Cancel(AsyncCodeActivityContext context)
{
    Pipeline pipeline = context.UserState as Pipeline;
    if (pipeline != null)
    {
        pipeline.Stop();
        DisposePipeline(pipeline);
    }
    base.Cancel(context);
}

AsyncCodeActivity 派生的活动可通过检查 IsCancellationRequested 属性来确定是否已请求取消,并可通过调用 MarkCanceled 方法将自身标记为已取消。