Modellazione del comportamento di annullamento nei flussi di lavoro

Le attività possono essere annullate all'interno di un flusso di lavoro, ad esempio da un'attività Parallel che annulla i rami incompleti quando la proprietà CompletionCondition restituisce true oppure all'esterno del flusso di lavoro, se l'host chiama il metodo Cancel. Per assicurare la gestione dell'annullamento, gli autori del flusso di lavoro possono usare le attività CancellationScope e CompensableActivity o creare attività personalizzate che forniscono la logica di annullamento. In questo argomento viene fornita una panoramica sull'annullamento nei flussi di lavoro.

Annullamento, compensazione e transazioni

Le transazioni consentono all'applicazione di interrompere, ovvero eseguire il rollback, tutte le modifiche eseguite all'interno della transazione se si verificano errori durante qualsiasi parte del processo di transazione. Tuttavia, non tutte le operazioni che potrebbero dover essere annullate sono appropriate per le transazioni, ad esempio operazioni con esecuzione prolungata o che non implicano risorse transazionali. La compensazione fornisce un modello per annullare operazioni non transazionali completate precedentemente se, in seguito, si verifica un errore nel flusso di lavoro. L'annullamento fornisce agli autori di flussi di lavoro e attività un modello per gestire le operazioni non transazionali che non sono state completate. Se l'attività viene annullata poiché non ne è stata completata l'esecuzione, sarà richiamata la relativa logica di annullamento, se disponibile.

Nota

Per altre informazioni sulle transazioni e sulla compensazione, vedere Transazioni e Compensazioni.

Utilizzo dell'attività CancellationScope

L'attività CancellationScope dispone di due sezioni che possono contenere le attività figlio Body e CancellationHandler. Nella proprietà Body vengono posizionate le attività che costituiscono la logica dell'attività e nella proprietà CancellationHandler vengono posizionate le attività che forniscono la logica di annullamento per l'attività. Un'attività può essere annullata solo se non è stata completata. Nel caso dell'attività CancellationScope, con completamento si intende il completamento delle attività nella proprietà Body. Se viene pianificata una richiesta di annullamento e le attività nella proprietà Body non sono state completate, l'attività CancellationScope sarà contrassegnata come Canceled e saranno eseguite le attività della proprietà CancellationHandler.

Annullamento di un flusso di lavoro dall'host

Un host può annullare un flusso di lavoro chiamando il metodo Cancel dell'istanza WorkflowApplication che sta ospitando il flusso di lavoro. Nell'esempio seguente, viene creato un flusso di lavoro che dispone di un'attività CancellationScope. Una volta richiamato il flusso di lavoro, l'host effettua una chiamata al metodo Cancel. L'esecuzione principale del flusso di lavoro viene interrotta, viene richiamata la proprietà CancellationHandler dell'attività CancellationScope, quindi il flusso di lavoro viene completato con lo stato 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 questo flusso di lavoro viene richiamato, nella console viene visualizzato l'output seguente.

Avvio del flusso di lavoro.
CancellationHandler richiamato.Flusso di lavoro b30ebb30-df46-4d90-a211-e31c38d8db3c annullato.

Nota

Quando un'attività CancellationScope viene annullata e viene richiamata la proprietà CancellationHandler, l'autore del flusso di lavoro deve determinare lo stato dell'attività prima del relativo annullamento al fine di fornire la logica di annullamento appropriata. La proprietà CancellationHandler non fornisce alcuna informazione sullo stato dell'attività annullata.

Un flusso di lavoro può essere annullato anche dall'host se un'eccezione non gestita viene propagata oltre la radice del flusso di lavoro e il gestore della proprietà OnUnhandledException restituisce Cancel. In questo esempio, viene avviato il flusso di lavoro che, successivamente, genera un'eccezione ApplicationException. Questa eccezione non viene gestita dal flusso di lavoro, pertanto viene richiamato il gestore della proprietà OnUnhandledException. Il gestore indica al runtime di annullare il flusso di lavoro e viene richiamata la proprietà CancellationHandler dell'attività CancellationScope attualmente in corso di esecuzione.

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 questo flusso di lavoro viene richiamato, nella console viene visualizzato l'output seguente.

Avvio del flusso di lavoro.
OnUnhandledException nel flusso di lavoro 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9È stata generata un'eccezione ApplicationException.Richiamato CancellationHandler.Flusso di lavoro 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 annullato.

Annullamento di un'attività dall'interno di un flusso di lavoro

Un'attività può essere annullata anche dal relativo elemento padre. Ad esempio, se un'attività Parallel dispone di più rami in esecuzione e la relativa proprietà CompletionCondition restituisce true, i rami incompleti corrispondenti saranno annullati. In questo esempio viene creata un'attività Parallel che dispone di due rami. La relativa proprietà CompletionCondition è impostata su true, pertanto l'oggetto Parallel viene completato non appena si verifica la stessa condizione per uno dei relativi rami. In questo esempio viene completato il ramo 2, pertanto il ramo 1 viene annullato.

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 questo flusso di lavoro viene richiamato, nella console viene visualizzato l'output seguente.

Branch 1 starting.
Branch 2 complete.Branch 1 canceled.Workflow e0685e24-18ef-4a47-acf3-5c638732f3be Completed. Le attività vengono annullate anche se un'eccezione viene propagata oltre la radice dell'attività, ma viene gestita a un livello superiore nel flusso di lavoro. In questo esempio la logica principale del flusso di lavoro è costituita da un'attività Sequence. Sequence è specificato come Body di un'attività CancellationScope che è contenuta da un'attività TryCatch. Dal corpo dell'attività Sequence viene generata un'eccezione che viene gestita dall'attività padre TryCatch. L'attività Sequence viene quindi annullata.

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 questo flusso di lavoro viene richiamato, nella console viene visualizzato l'output seguente.

Avvio dell'attività Sequence.
Sequence canceled.Exception caught.Workflow e3c18939-121e-4c43-af1c-ba1ce977ce55 Completed.

Generazione di eccezioni da un oggetto CancellationHandler

Qualsiasi eccezione generata dalla proprietà CancellationHandler di un'attività CancellationScope è irreversibile per il flusso di lavoro. Se è possibile che le eccezioni escano da una proprietà CancellationHandler, usare un oggetto TryCatch nella proprietà CancellationHandler per rilevare e gestire tali eccezioni.

Annullamento tramite l'oggetto CompensableActivity

Analogamente all'attività CancellationScope, l'oggetto CompensableActivity dispone di una proprietà CancellationHandler. Se un oggetto CompensableActivity viene annullato, viene richiamata qualsiasi attività nella relativa proprietà CancellationHandler. Tale operazione può essere utile per annullare operazioni compensabili completate parzialmente. Per informazioni su come usare CompensableActivity per la compensazione e l'annullamento, vedere Compensazione.

Annullamento tramite attività personalizzate

Gli autori di attività personalizzate possono implementare la logica di annullamento nelle proprie attività personalizzate in molti modi diversi. Le attività personalizzate che derivano da Activity possono implementare la logica di annullamento inserendo CancellationScope o un'altra attività personalizzata che contiene la logica di annullamento nel corpo dell'attività. Le attività derivate AsyncCodeActivity e NativeActivity possono eseguire l'override del rispettivo metodo Cancel e fornire qui la logica di annullamento. Le attività derivate CodeActivity non forniscono alcuna misura per l'annullamento poiché tutto il lavoro viene eseguito in un unico picco di esecuzione quando il runtime chiama il metodo Execute. Se il metodo Execute non è ancora stato chiamato e viene annullata un'attività basata sull'oggetto CodeActivity, l'attività viene chiusa con lo stato Canceled e il metodo Execute non viene chiamato.

Annullamento tramite l'oggetto NativeActivity

Le classi derivate NativeActivity possono eseguire l'override del metodo Cancel per fornire la logica di annullamento personalizzata. Se questo metodo non viene sottoposto a override, viene applicata la logica di annullamento del flusso di lavoro predefinita. L'annullamento predefinito è il processo che si verifica per un oggetto NativeActivity che non esegue l'override del metodo Cancel o il cui metodo Cancel chiama il metodo Cancel dell'oggetto NativeActivity base. Quando un'attività viene annullata, il runtime contrassegna l'attività per l'annullamento e gestisce automaticamente la pulizia. Se l'attività dispone solo di segnalibri in attesa, questi ultimi saranno rimossi e l'attività sarà contrassegnata come Canceled. Qualsiasi attività figlio in attesa dell'attività annullata sarà annullata a sua volta. Qualsiasi tentativo di pianificare attività figlio aggiuntive verrà ignorato e l'attività sarà contrassegnata come Canceled. Se qualsiasi attività figlio in attesa viene completata nello stato Canceled o Faulted, l'attività sarà contrassegnata come Canceled. Si noti che è possibile ignorare una richiesta di annullamento. Se un'attività non dispone di segnalibri in attesa o di attività figlio in esecuzione e non pianifica alcun elemento di lavoro aggiuntivo dopo essere stata contrassegnata per l'annullamento, verrà completata correttamente. Questo annullamento predefinito è sufficiente per molti scenari, tuttavia se è necessaria un'ulteriore logica di annullamento, è possibile usare le attività di annullamento incorporate o le attività personalizzate.

Nell'esempio seguente, viene definito l'override Cancel di un'attività NativeActivity personalizzata basata sull'oggetto ParallelForEach. Quando l'attività viene annullata, questo override gestisce la logica di annullamento per l'attività. Questo esempio fa parte dell'esempio ParallelForEach non generico.

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

Le attività derivate da NativeActivity possono determinare se l'annullamento è stato richiesto in seguito al controllo della proprietà IsCancellationRequested e possono contrassegnarsi come annullate chiamando il metodo MarkCanceled. La chiamata al metodo MarkCanceled non completa immediatamente l'attività. Come al solito, il runtime completerà l'attività quando non dispone più di alcuna operazione in attesa, tuttavia se viene chiamato il metodo MarkCanceled, lo stato finale sarà Canceled anziché Closed.

Annullamento tramite l'oggetto AsyncCodeActivity

Anche le attività basate sull'oggetto AsyncCodeActivity possono fornire la logica di annullamento personalizzata eseguendo l'override del metodo Cancel. Se questo metodo non viene sottoposto a override, non viene eseguita alcuna gestione dell'annullamento se l'attività viene annullata. Nell'esempio seguente, viene definito l'override Cancel di un'attività AsyncCodeActivity personalizzata basata sull'oggetto ExecutePowerShell. Quando l'attività viene annullata, esegue il comportamento di annullamento desiderato.

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

Le attività derivate da AsyncCodeActivity possono determinare se l'annullamento è stato richiesto in seguito al controllo della proprietà IsCancellationRequested e possono contrassegnarsi come annullate chiamando il metodo MarkCanceled.