Compartilhar via


Modelando o comportamento cancelar em fluxos de trabalho

As atividades podem ser canceladas em um fluxo de trabalho, por exemplo por uma atividade de Parallel que cancela ramificações incompletos quando seu CompletionCondition avalia a true, ou fora de fluxo de trabalho, se o host chama Cancel. Para fornecer tratamento cancelar, os autores de fluxo de trabalho podem usar a atividade de CancellationScope , a atividade de CompensableActivity , ou crie as atividades personalizados que fornecem lógica cancelar. Este tópico fornece uma visão geral de cancelamento em fluxos de trabalho.

Cancelar, compensação, e transações

As transações fornecem ao seu aplicativo a capacidade de nulo () para reverter as alterações executadas dentro de transação se qualquer erro ocorre durante qualquer parte do processo de transação. No entanto, nem todo o trabalho que precise ser cancelado ou desfeito é apropriado para transações, como o trabalho longo ou o trabalho que não envolvem recursos transacionais. A compensação fornece um modelo para desfazer o trabalho não transacional anteriormente concluído se houver uma falha subsequente no fluxo de trabalho. Cancelamento fornece um modelo para autores de fluxo de trabalho e atividade do trabalho não transacional de forma que não foi concluído. Se uma atividade não concluiu sua execução e será cancelada, sua lógica cancelar será chamada está disponível.

Observação

Para obter mais informações sobre transações e compensação, consulte Transações e Compensação.

Usando CancellationScope

A atividade de CancellationScope tem duas seções que podem conter atividades filhos: Body e CancellationHandler. Body é onde as atividades que compõem a lógica de atividade são colocadas, e CancellationHandler é onde as atividades que fornecem lógica cancelar para atividades são colocadas. Uma atividade pode ser cancelada somente se não tiver concluído. No caso de atividade de CancellationScope , o preenchimento refere-se a conclusão de atividades em Body. Se uma solicitação cancelar é agendada e as atividades em Body não tiverem terminado, então CancellationScope será marcado como Canceled e as atividades de CancellationHandler serão executadas.

Cancelando um fluxo de trabalho do host

Um host pode cancelar um fluxo de trabalho chamando o método Cancel de instância de WorkflowApplication que está hospedando o fluxo de trabalho. No exemplo a seguir um fluxo de trabalho é criado que tem CancellationScope. O fluxo de trabalho é chamado, e então o host uma chamada a Cancel. A execução do fluxo de trabalho é interrompida, CancellationHandler de CancellationScope é chamado, e então o fluxo de trabalho termina com um status de 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();

Quando esse fluxo de trabalho é chamado, a seguinte saída é exibida no console.

Iniciando o fluxo de trabalho.
CancellationHandler invocado.Fluxo de trabalho b30ebb30-df46-4d90-a211-e31c38d8db3c Canceled.

Observação

Quando uma atividade de CancellationScope é cancelada e CancellationHandler é invocado, foi responsabilidade do autor de fluxo de trabalho determinar o progresso que a atividade cancelada feita antes que foi cancelado para fornecer a lógica apropriada cancelar. CancellationHandler não fornece nenhuma informação sobre o andamento de atividade cancelada.

Um fluxo de trabalho também pode ser cancelado host se uma exceção não tratada borbulha anterior após a raiz de fluxo de trabalho e o manipulador de OnUnhandledException retorna Cancel. Nesse exemplo inicia o fluxo de trabalho e lançam em ApplicationException. Esta exceção é não tratados pelo fluxo de trabalho e então o manipulador de OnUnhandledException é chamado. O manipulador instrui o runtime para cancelar o fluxo de trabalho, e CancellationHandler atividade estão atualmente em execução de CancellationScope é chamado.

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();

Quando esse fluxo de trabalho é chamado, a seguinte saída é exibida no console.

Iniciando o fluxo de trabalho.
OnUnhandledException no Fluxo de trabalho 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9Um ApplicationException foi lançado.CancellationHandler invocado.Fluxo de trabalho 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 Canceled.

Cancelando uma atividade de dentro de um fluxo de trabalho

Uma atividade também pode ser cancelada por seu pai. Por exemplo, se uma atividade de Parallel tem várias ramificações e que executa o CompletionCondition avalia a true suas ramificações incompletos serão canceladas em seguida. Nesse exemplo uma atividade de Parallel é criada que possui duas ramificações. O CompletionCondition é definido como true portanto Parallel concluir o que qualquer uma de suas ramificações terminar. Nesta ramificação 2 do exemplo completo, e assim que a 1 é cancelado.

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();

Quando esse fluxo de trabalho é chamado, a seguinte saída é exibida no console.

Ramificação 1 que inicia.
Ramificação 2 concluída.Ramificação 1 cancelada.Fluxo de trabalho e0685e24-18ef-4a47-acf3-5c638732f3be concluído. As atividades também são canceladas se surgir uma exceção para além da raiz da atividade mas é tratada em um nível mais alto do fluxo de trabalho. Nesse exemplo, a principal lógica de fluxo de trabalho consiste em uma atividade de Sequence . Sequence é especificado como Body de uma atividade de CancellationScope que está contida por uma atividade de TryCatch . Uma exceção é lançada do corpo de Sequence, é tratada pela atividade pai de TryCatch , e Sequence é cancelado.

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();

Quando esse fluxo de trabalho é chamado, a seguinte saída é exibida no console.

Iniciar a sequência.
Sequência cancelada.Exceção capturada.Fluxo de trabalho e3c18939-121e-4c43-af1c-ba1ce977ce55 concluído.

Gerar exceções de um CancellationHandler

Todas as exceções geradas de CancellationHandler de CancellationScope são fatais ao fluxo de trabalho. Se houver uma chance de exceções que escapam de CancellationHandler, use TryCatch em CancellationHandler para capturar e tratar essas exceções.

Cancelar usando CompensableActivity

Como a atividade de CancellationScope , CompensableActivity tem CancellationHandler. Se CompensableActivity é cancelado, todas as atividades em seu CancellationHandler são chamadas. Isso pode ser útil para desfazer o trabalho compensável parcialmente concluído. Para obter informações sobre como usar CompensableActivity para a compensação e cancelamento, consulte Compensação.

Cancelar usando atividades personalizados

Os autores de atividade personalizados podem implementar a lógica cancelar nas atividades personalizados em várias maneiras diferentes. As atividades personalizados que derivam de Activity podem implementar a lógica de cancelamento colocando CancellationScope ou outra atividade personalizado que contém a lógica cancelar no corpo da atividade. AsyncCodeActivity e as atividades NativeActivity derivadas podem substituir o método respectivo de Cancel e fornecer a lógica de cancelamento lá. CodeActivity derivado atividades não fornece nenhuma para o cancelamento como todo o trabalho é executado em uma única explosão de execução quando o runtime chama o método de Execute . Se o método executar não foi chamado ainda e uma atividade com base CodeActivity será cancelada, a atividade fecha com um status de Canceled e o método de Execute não é chamado.

Cancelar usando NativeActivity

NativeActivity derivado atividades pode substituir o método de Cancel para fornecer a lógica personalizada cancelar. Se este método não é substituído, então a lógica padrão de cancelamento de fluxo de trabalho é aplicada. Cancelar o padrão é o processo que ocorre para um NativeActivity que não substitui o método Cancel ou cujo método Cancel chama o método base NativeActivity Cancel. Quando uma atividade é cancelada, o runtime sinaliza a atividade para o botão e automaticamente trata determinada limpeza. Se a atividade tem apenas indicadores pendentes, os indicadores serão removidos e a atividade será marcada como Canceled. Todas as atividades filhos excelentes de atividade cancelada serão canceladas por sua vez. Qualquer tentativa de agendar atividades filho adicionais resultará na tentativa que está sendo ignorada e a atividade será marcada como Canceled. Se alguma atividade excelente filho termina em Canceled ou estado de Faulted , então a atividade será marcada como Canceled. Observe que uma solicitação de cancelamento pode ser ignorada. Se uma atividade não tem nenhum indicadores pendentes ou executar atividades filhos e não agenda os itens de trabalho adicionais após o embandeiramento para cancelamento, se concluirá com êxito. Este padrão cancelar basta para muitos cenários, mas se a lógica adicional cancelar é necessária, então as atividades internos de cancelamento ou as atividades personalizados podem ser usadas.

No exemplo a seguir, a substituição de Cancel de uma atividade personalizada com base NativeActivity de ParallelForEach é definida. Quando a atividade é cancelada, alças desta substituição lógica cancelar para atividades. Este exemplo faz parte do exemplo ParallelForEach não genérico.

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 derivado atividades pode determinar se o cancelar foi solicitado inspecionando a propriedade de IsCancellationRequested , e marcar-se como canceladas chamando o método MarkCanceled . A chamada MarkCanceled não concluir imediatamente a atividade. Como de costume, o runtime se concluirá a atividade quando não tem não mais trabalho excelente, mas se MarkCanceled é chamado estado final será Canceled em vez de Closed.

Cancelar usando AsyncCodeActivity

AsyncCodeActivity com atividades também pode fornecer a lógica personalizada cancelar substituindo o método de Cancel . Se este método não é substituído, então nenhuma tratamento cancelar é executada se a atividade é cancelada. No exemplo a seguir, a substituição de Cancel de uma atividade personalizada com base AsyncCodeActivity de ExecutePowerShell é definida. Quando a atividade é cancelada, executa o comportamento desejado cancelar.

// 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 derivado atividades pode determinar se o cancelar foi solicitado inspecionando a propriedade de IsCancellationRequested , e marcar-se como canceladas chamando o método MarkCanceled .