Partager via


Modélisation du comportement d'annulation dans les workflows

Les activités peuvent être annulées à l’intérieur d’un workflow, par exemple par une activité Parallel qui annule des branches incomplètes lorsque son CompletionCondition a la valeur true, ou à l’extérieur du workflow, si l’hôte appelle Cancel. Pour fournir la gestion des annulations, les auteurs de workflow peuvent utiliser l'activité CancellationScope, l'activité CompensableActivity ou créer des activités personnalisées qui fournissent la logique d'annulation. Cette rubrique fournit une vue d'ensemble de l'annulation dans les workflows.

Annulation, compensation et transactions

Les transactions permettent à votre application d'annuler (restaurer) toute modification exécutée dans une transaction en cas d'erreur au cours du processus de transaction. Toutefois, le travail qui peut devoir être annulé n’est pas dans sa totalité approprié pour les transactions, tel que le travail de longue durée ou le travail qui n’implique pas de ressources transactionnelles. La compensation fournit un modèle pour l'annulation de travail non transactionnel précédemment effectué en cas d'échec ultérieur dans le workflow. L’annulation fournit un modèle pour les auteurs de workflow et d’activité pour gérer le travail non transactionnel qui n’a pas été effectué. Si une activité n'a pas terminé son exécution et est annulée, sa logique d'annulation sera appelée si elle est disponible.

Notes

Pour plus d’informations sur les transactions et la compensation, consultez Transactions et Compensation.

Utilisation de CancellationScope

L'activité CancellationScope a deux sections qui peuvent contenir des activités enfants : Body et CancellationHandler. Le Body est l'endroit où les activités qui composent la logique de l'activité sont placées et le CancellationHandler est l'endroit où les activités qui fournissent la logique d'annulation pour l'activité sont placées. Une activité peut être annulée uniquement si elle n'est pas terminée. Dans le cas de l'activité CancellationScope, l'achèvement fait référence à l'achèvement des activités dans le Body. Si une demande d'annulation est planifiée et que les activités dans le Body ne sont pas terminées, le CancellationScope sera marqué comme Canceled et les activités CancellationHandler seront exécutées.

Annulation d'un workflow à partir de l'hôte

Un hôte peut annuler un workflow en appelant la méthode Cancel de l'instance WorkflowApplication qui héberge le workflow. Dans l'exemple suivant, un workflow avec un CancellationScope est créé. Le workflow est appelé, puis l'hôte passe un appel au Cancel. L'exécution principale du workflow est arrêtée, le CancellationHandler du CancellationScope est appelé, puis le workflow se termine avec l'état 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();

Lorsque ce workflow est appelé, la sortie suivante s'affiche sur la console.

Starting the workflow.
CancellationHandler invoked.Workflow b30ebb30-df46-4d90-a211-e31c38d8db3c Canceled.

Notes

Lorsqu'une activité CancellationScope est annulée et que le CancellationHandler est appelé, il incombe à l'auteur de workflow de déterminer la progression effectuée par l'activité avant son annulation afin de fournir la logique d'annulation appropriée. Le CancellationHandler ne fournit aucune information sur la progression de l'activité annulée.

Un workflow peut également être annulé à partir de l'hôte si une exception non gérée est propagée au-delà de la racine du workflow et que le gestionnaire OnUnhandledException retourne Cancel. Dans cet exemple, le workflow démarre, puis lève un ApplicationException. Cette exception n'étant pas prise en charge par le workflow, le gestionnaire OnUnhandledException est appelé. Le gestionnaire indique à l'exécution d'annuler le workflow et le CancellationHandler de l'activité CancellationScope actuellement en cours d'exécution est appelé.

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

Lorsque ce workflow est appelé, la sortie suivante s'affiche sur la console.

Starting the workflow.
OnUnhandledException in Workflow 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9An ApplicationException was thrown.CancellationHandler invoked.Workflow 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 Canceled.

Annulation d'une activité à l'intérieur d'un workflow

Une activité peut également être annulée par son parent. Par exemple, si une activité Parallel a plusieurs branches en cours d’exécution et que son CompletionCondition a la valeur true, ses branches incomplètes seront annulées. Dans cet exemple, une activité Parallel avec deux branches est créée. Son CompletionCondition ayant la valeur true, le Parallel est effectué dès que l’une de ses branches est terminée. Dans cet exemple, la branche 2 étant terminée, la branche 1 est annulée.

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

Lorsque ce workflow est appelé, la sortie suivante s'affiche sur la console.

Branch 1 starting.
Branch 2 complete.Branch 1 canceled.Workflow e0685e24-18ef-4a47-acf3-5c638732f3be Completed. Les activités sont également annulées si une exception est levée après le démarrage de l’activité, mais elle est traitée au niveau supérieur dans le workflow. Dans cet exemple, la logique principale du workflow se compose d'une activité Sequence. Le Sequence est spécifié comme Body d'une activité CancellationScope contenue dans une activité TryCatch. Une exception est levée à partir du corps du Sequence, est gérée par l'activité TryCatch parente et le Sequence est annulé.

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

Lorsque ce workflow est appelé, la sortie suivante s'affiche sur la console.

Sequence starting.
Sequence canceled.Exception caught.Workflow e3c18939-121e-4c43-af1c-ba1ce977ce55 Completed.

Levée d'exceptions à partir d'un CancellationHandler

Toutes les exceptions levées à partir du CancellationHandler d'un CancellationScope sont irrécupérables pour le workflow. S'il existe une possibilité pour les exceptions d'échapper à un CancellationHandler, utilisez un TryCatch dans le CancellationHandler pour intercepter et gérer ces exceptions.

Annulation à l'aide de CompensableActivity

Comme l'activité CancellationScope, le CompensableActivity a un CancellationHandler. Si un CompensableActivity est annulé, toutes les activités dans son CancellationHandler sont appelées. Cela peut être utile pour l'annulation de travail compensable partiellement effectué. Pour plus d’informations sur l’utilisation de CompensableActivity pour la compensation et l’annulation, consultez Compensation.

Annulation à l'aide d'activités personnalisées

Les auteurs d'activités personnalisés peuvent implémenter une logique d'annulation dans leurs activités personnalisées de plusieurs façons différentes. Les activités personnalisées qui dérivent de Activity peuvent implémenter la logique d'annulation en plaçant CancellationScope ou une autre activité personnalisée qui contient la logique d'annulation dans le corps de l'activité. Les activités dérivées AsyncCodeActivity et NativeActivity peuvent remplacer leur méthode correspondante Cancel et fournir la logique d'annulation à cet endroit. Les activités dérivées CodeActivity ne fournissent aucune configuration pour l'annulation parce que tout leur travail est effectué dans une rafale unique d'exécution lorsque l'exécution appelle la méthode Execute. Si la méthode d'exécution n'a pas encore été appelée et qu'une activité basée sur CodeActivity est annulée, l'activité est fermée avec l'état Canceled et la méthode Execute n'est pas appelée.

Annulation à l'aide de NativeActivity

Les activités dérivées NativeActivity peuvent substituer la méthode Cancel pour fournir une logique d'annulation personnalisée. Si cette méthode n'est pas substituée, la logique d'annulation du workflow par défaut est appliquée. L’annulation par défaut est le processus qui se produit pour un NativeActivity qui ne remplace pas la méthode Cancel ou dont la méthode Cancel appelle la méthode NativeActivityCancel de base. Lorsqu'une activité est annulée, l'exécution signale l'activité pour annulation et gère automatiquement un certain nettoyage. Si l'activité a seulement des signets en attente, les signets seront supprimés et l'activité sera marquée comme Canceled. Toutes les activités enfants en attente de l'activité annulée seront à leur tour annulées. Toute tentative de planifier des activités enfants supplémentaires aura pour conséquence que la tentative sera ignorée et l'activité sera marquée comme Canceled. Si une activité enfant en attente se termine dans l'état Canceled ou Faulted, l'activité sera marquée comme Canceled. Notez qu'une demande d'annulation peut être ignorée. Si une activité n'a pas de signets en attente ou d'activités enfants en cours d'exécution et ne planifie pas d'éléments de travail supplémentaires après avoir été signalée pour annulation, son exécution sera réussie. Cette annulation par défaut suffit pour de nombreux scénarios mais, si une logique d'annulation supplémentaire est nécessaire, les activités d'annulation intégrées ou activités personnalisées peuvent être utilisées.

Dans l'exemple suivant, la substitution Cancel d'une activité NativeActivity personnalisée basée sur ParallelForEach est définie. Lorsque l'activité est annulée, cette substitution gère la logique d'annulation pour l'activité. Cet exemple fait partie de l’exemple ParallelForEach non générique.

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

Les activités dérivées NativeActivity peuvent déterminer si l'annulation a été demandée en inspectant la propriété IsCancellationRequested, puis se marquer comme annulées en appelant la méthode MarkCanceled. L'appel de MarkCanceled ne met pas immédiatement fin à l'activité. Comme habituellement, l'exécution termine l'activité lorsque plus aucun travail n'est en attente, mais si MarkCanceled est appelé, l'état définitif sera Canceled au lieu de Closed.

Annulation à l'aide d'AsyncCodeActivity

Les activités basées sur AsyncCodeActivity peuvent également fournir une logique d'annulation personnalisée en substituant la méthode Cancel. Si cette méthode n'est pas substituée, aucune gestion des annulations n'est effectuée si l'activité est annulée. Dans l'exemple suivant, la substitution Cancel d'une activité AsyncCodeActivity personnalisée basée sur ExecutePowerShell est définie. Lorsque l'activité est annulée, elle exécute le comportement d'annulation voulu.

// 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);
}

Les activités dérivées AsyncCodeActivity peuvent déterminer si l'annulation a été demandée en inspectant la propriété IsCancellationRequested, puis se marquer comme annulées en appelant la méthode MarkCanceled.