Partilhar via


Modelando o comportamento de cancelamento em fluxos de trabalho

As atividades podem ser canceladas dentro de um fluxo de trabalho, por exemplo, por uma Parallel atividade cancelando ramificações incompletas quando é CompletionCondition avaliada para true, ou de fora do fluxo de trabalho, se o host chamar Cancel. Para fornecer tratamento de cancelamento, os autores do fluxo de trabalho podem usar a CancellationScope atividade, a CompensableActivity atividade ou criar atividades personalizadas que forneçam lógica de cancelamento. Este tópico fornece uma visão geral do cancelamento em fluxos de trabalho.

Cancelamento, compensação e transações

As transações dão ao seu aplicativo a capacidade de anular (reverter) todas as alterações executadas dentro da transação se ocorrerem erros durante qualquer parte do processo de transação. No entanto, nem todo o trabalho que pode precisar ser cancelado ou desfeito é apropriado para transações, como trabalho de longa duração ou trabalho que não envolve recursos transacionais. A compensação fornece um modelo para desfazer trabalho não transacional concluído anteriormente se houver uma falha subsequente no fluxo de trabalho. O cancelamento fornece um modelo para os autores de fluxo de trabalho e atividade lidarem com o trabalho não transacional que não foi concluído. Se uma atividade não tiver concluído sua execução e for cancelada, sua lógica de cancelamento será invocada se estiver disponível.

Nota

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

Usando o CancellationScope

A CancellationScope atividade tem duas seções que podem conter atividades infantis: Body e CancellationHandler. O Body é onde as atividades que compõem a lógica da atividade são colocadas, e é onde as CancellationHandler atividades que fornecem lógica de cancelamento para a atividade são colocadas. Uma atividade só pode ser cancelada se não tiver sido concluída. No caso da atividade, a CancellationScope conclusão refere-se à conclusão das atividades no Body. Se um pedido de cancelamento for agendado e as atividades no Body não tiverem sido concluídas, então o CancellationScope será marcado como Canceled e as CancellationHandler atividades serão executadas.

Cancelando um fluxo de trabalho do host

Um host pode cancelar um fluxo de trabalho chamando o Cancel método da instância que está hospedando o fluxo de WorkflowApplication trabalho. No exemplo a seguir, é criado um fluxo de trabalho com um CancellationScopearquivo . O fluxo de trabalho é invocado e, em seguida, o host faz uma chamada para Cancel. A execução principal do fluxo de trabalho é interrompida, o CancellationHandler do é invocado e, em seguida, o fluxo de CancellationScope trabalho é concluído 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 é invocado, a saída a seguir é exibida no console.

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

Nota

Quando uma CancellationScope atividade é cancelada e invocada CancellationHandler , é responsabilidade do autor do fluxo de trabalho determinar o progresso que a atividade cancelada fez antes de ser cancelada, a fim de fornecer a lógica de cancelamento apropriada. O CancellationHandler não fornece qualquer informação sobre o progresso da atividade cancelada.

Um fluxo de trabalho também pode ser cancelado do host se uma exceção não tratada borbulhar além da raiz do fluxo de trabalho e o OnUnhandledException manipulador retornar Cancel. Neste exemplo, o fluxo de trabalho é iniciado e, em seguida, lança um ApplicationExceptionarquivo . Essa exceção não é tratada pelo fluxo de trabalho e, portanto, o OnUnhandledException manipulador é invocado. O manipulador instrui o tempo de execução a cancelar o fluxo de trabalho e a CancellationHandler atividade em execução CancellationScope no momento é invocada.

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 é invocado, a saída a seguir é exibida no console.

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

Cancelando uma atividade de dentro de um fluxo de trabalho

Uma atividade também pode ser cancelada pelo pai. Por exemplo, se uma Parallel atividade tiver várias ramificações em execução e for CompletionCondition avaliada, suas ramificações true incompletas serão canceladas. Neste exemplo, é criada uma Parallel atividade que tem duas ramificações. É CompletionCondition definido para true que o completo assim que qualquer Parallel um dos seus ramos é concluído. Neste exemplo, a ramificação 2 é concluída e, portanto, a ramificação 1 é cancelada.

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 é invocado, a saída a seguir é exibida no console.

Início do ramo 1.
Ramal 2 concluído.Ramo 1 cancelado.Fluxo de trabalho e0685e24-18ef-4a47-acf3-5c638732f3be Concluído. As atividades também são canceladas se uma exceção borbulhar além da raiz da atividade, mas for tratada em um nível mais alto no fluxo de trabalho. Neste exemplo, a lógica principal do fluxo de trabalho consiste em uma Sequence atividade. O Sequence é especificado como o Body de uma CancellationScope atividade que está contida por uma TryCatch atividade. Uma exceção é lançada do corpo do , é manipulada Sequencepela atividade pai TryCatch e a Sequence é cancelada.

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 é invocado, a saída a seguir é exibida no console.

Início da sequência.
Sequência cancelada.Exceção apreendida.Workflow e3c18939-121e-4c43-af1c-ba1ce977ce55 Concluído.

Lançando exceções de um CancellationHandler

Quaisquer exceções lançadas a CancellationHandler partir do de um CancellationScope são fatais para o fluxo de trabalho. Se houver a possibilidade de exceções escaparem de um CancellationHandler, use a TryCatch no CancellationHandler para capturar e lidar com essas exceções.

Cancelamento usando CompensableActivity

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

Cancelamento usando atividades personalizadas

Os autores de atividades personalizadas podem implementar a lógica de cancelamento em suas atividades personalizadas de várias maneiras diferentes. As atividades personalizadas derivadas podem implementar a lógica de Activity cancelamento colocando uma CancellationScope ou outra atividade personalizada que contenha lógica de cancelamento no corpo da atividade. AsyncCodeActivity e NativeActivity as atividades derivadas podem substituir seu respetivo Cancel método e fornecer lógica de cancelamento lá. CodeActivity As atividades derivadas não fornecem nenhuma provisão para cancelamento porque todo o seu trabalho é executado em uma única explosão de execução quando o tempo de execução chama o Execute método. Se o método execute ainda não tiver sido chamado e uma CodeActivity atividade baseada for cancelada, a atividade será fechada com um status de Canceled e o Execute método não será chamado.

Cancelamento usando NativeActivity

NativeActivity As atividades derivadas podem substituir o Cancel método para fornecer lógica de cancelamento personalizada. Se esse método não for substituído, a lógica de cancelamento do fluxo de trabalho padrão será aplicada. O cancelamento padrão é o processo que ocorre para um NativeActivity que não substitui o Cancel método ou cujo Cancel método chama o método base NativeActivity Cancel . Quando uma atividade é cancelada, o tempo de execução sinaliza a atividade para cancelamento e lida automaticamente com determinada limpeza. Se a atividade tiver apenas marcadores pendentes, os marcadores serão removidos e a atividade será marcada como Canceled. Quaisquer atividades infantis pendentes da atividade cancelada serão, por sua vez, canceladas. Qualquer tentativa de agendar atividades adicionais para crianças resultará na tentativa ser ignorada e a atividade será marcada como Canceled. Se alguma atividade infantil pendente for concluída no estado ou Faulted , a atividade será marcada Canceled como Canceled. Tenha em atenção que um pedido de cancelamento pode ser ignorado. Se uma atividade não tiver nenhum marcador pendente ou executar atividades infantis e não agendar nenhum item de trabalho adicional depois de ser sinalizada para cancelamento, ela será concluída com êxito. Esse cancelamento padrão é suficiente para muitos cenários, mas se for necessária uma lógica de cancelamento adicional, as atividades de cancelamento internas ou as atividades personalizadas podem ser usadas.

No exemplo a seguir, a Cancel substituição de uma NativeActivity atividade personalizada ParallelForEach baseada é definida. Quando a atividade é cancelada, essa substituição lida com a lógica de cancelamento da atividade. 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 As atividades derivadas podem determinar se o cancelamento foi solicitado inspecionando a propriedade e marcar-se como cancelada IsCancellationRequested chamando o MarkCanceled método. A chamada MarkCanceled não conclui imediatamente a atividade. Como de costume, o tempo de execução concluirá a atividade quando não tiver mais trabalho pendente, mas se MarkCanceled for chamado o estado final será Canceled em vez de Closed.

Cancelamento usando AsyncCodeActivity

AsyncCodeActivity As atividades baseadas também podem fornecer lógica de cancelamento personalizada substituindo o Cancel método. Se esse método não for substituído, nenhum tratamento de cancelamento será executado se a atividade for cancelada. No exemplo a seguir, a Cancel substituição de uma AsyncCodeActivity atividade personalizada ExecutePowerShell baseada é definida. Quando a atividade é cancelada, ela executa o comportamento de cancelamento desejado.

// 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 As atividades derivadas podem determinar se o cancelamento foi solicitado inspecionando a propriedade e marcar-se como cancelada IsCancellationRequested chamando o MarkCanceled método.