Modelování chování zrušení v pracovních postupech

Aktivity mohou být zrušeny uvnitř pracovního postupu, například když aktivita Parallel zruší neúplné větve, když její CompletionCondition vyhodnocení odpovídá true, nebo mimo pracovní postup, pokud hostitel volá Cancel. K zajištění zpracování zrušení mohou autoři pracovního postupu použít aktivitu CancellationScope, aktivitu CompensableActivity nebo vytvořit vlastní aktivity, které poskytují logiku zrušení. Toto téma obsahuje přehled zrušení v pracovních postupech.

Zrušení, kompenzace a transakce

Transakce umožňují vaší aplikaci přerušit (vrátit zpět) všechny změny provedené v rámci transakce, pokud dojde k nějakým chybám během libovolné části procesu transakce. Ne všechna práce, která může být potřeba zrušit nebo vrátit zpět, je však vhodná pro transakce, jako je dlouhotrvající práce nebo práce, která nezahrnuje transakční prostředky. Kompenzace poskytuje model pro zrušení dříve dokončené non-transakční práce, pokud dojde k následnému selhání pracovního postupu. Zrušení poskytuje autorům pracovních postupů a aktivit model, jak zpracovávat ne-transakční práci, která nebyla dokončena. Pokud aktivita nedokončila spuštění a zruší se, vyvolá se její logika zrušení, pokud je k dispozici.

Poznámka:

Další informace o transakcích a kompenzacích naleznete v tématu Transakce a kompenzace.

Použití CancellationScope

Aktivita CancellationScope má dvě části, jež mohou obsahovat podřízené aktivity: Body a CancellationHandler. Tam Body se nacházejí aktivity, které tvoří logiku aktivity, a CancellationHandler tam se nacházejí aktivity, které poskytují logiku zrušení aktivity. Aktivitu je možné zrušit pouze v případě, že se nedokončila. V případě aktivity CancellationScope se dokončení týká dokončení aktivit v Body. Pokud je naplánována žádost o zrušení a aktivity v Body nejsou dokončeny, pak bude CancellationScope označen jako Canceled a aktivity CancellationHandler se spustí.

Zrušení pracovního postupu od hostitele

Hostitel může pracovní postup zrušit voláním Cancel metody WorkflowApplication instance, která je hostitelem pracovního postupu. V následujícím příkladu je vytvořen pracovní postup, který má CancellationScope. Pracovní postup je spuštěn a hostitel zavolá Cancel. Hlavní spuštění pracovního postupu je zastaveno, je vyvolán CancellationHandler z CancellationScope, a pracovní postup se dokončí se stavem 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 {e.InstanceId} Terminated.");
        Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
    }
    else
    {
        Console.WriteLine($"Workflow {e.InstanceId} Completed.");
    }
};

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

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

wfApp.Cancel();

Při vyvolání tohoto pracovního postupu se v konzole zobrazí následující výstup.

Spuštění pracovního postupu.Vyvolána obslužná rutina zrušení.Pracovní postup b30ebb30-df46-4d90-a211-e31c38d8db3c zrušen.

Poznámka:

CancellationScope Když je aktivita zrušena a CancellationHandler vyvolána, je odpovědností autora pracovního postupu určit průběh zrušené aktivity před zrušením, aby poskytl odpovídající logiku zrušení. Neposkytuje CancellationHandler žádné informace o průběhu zrušené aktivity.

Pracovní postup lze také zrušit z hostitele, pokud neošetřená výjimka probublá nad kořen pracovního postupu a obslužná rutina OnUnhandledException vrátí Cancel. V tomto příkladu se pracovní postup spustí a následně vyvolá chybu ApplicationException. Tato výjimka není zpracovaná pracovním postupem, a proto je vyvolána obslužná rutina OnUnhandledException . Obslužná rutina dává modulu runtime pokyn k zrušení pracovního postupu, čímž se vyvolá aktuálně spuštěná aktivita CancellationHandler.

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 {e.InstanceId}\n{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 {e.InstanceId} Terminated.");
        Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
    }
    else
    {
        Console.WriteLine($"Workflow {e.InstanceId} Completed.");
    }
};

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

Při vyvolání tohoto pracovního postupu se v konzole zobrazí následující výstup.

Spuštění pracovního postupu."OnUnhandledException" v pracovním postupu 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9Byla vyvolána ApplicationException.Vyvolána obslužná rutina zrušení.Pracovní postup 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 zrušeno.

Zrušení aktivity z pracovního postupu

Aktivitu může zrušit také její nadřazená aktivita. Například pokud má aktivita Parallel více spuštěných větví a její CompletionCondition vyhodnocení je true, pak její neúplné větve budou zrušeny. V tomto příkladu Parallel se vytvoří aktivita, která má dvě větve. Její CompletionCondition je nastavena na true, aby se Parallel dokončila, jakmile se dokončí jakákoli z jejích větví. V tomto příkladu se větev 2 dokončí a větev 1 se zruší.

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 {e.InstanceId} Terminated.");
        Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
    }
    else
    {
        Console.WriteLine($"Workflow {e.InstanceId} Completed.");
    }
};

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

Při vyvolání tohoto pracovního postupu se v konzole zobrazí následující výstup.

Větev 1 začíná.Větev 2 je hotová.Větev 1 byla zrušena.Pracovní postup e0685e24-18ef-4a47-acf3-5c638732f3be Dokončeno. Aktivity se také zruší, pokud se v pracovním postupu vytvoří bublina výjimky nad kořenem aktivity, ale zpracovává se na vyšší úrovni pracovního postupu. V tomto příkladu se hlavní logika pracovního postupu skládá z Sequence aktivity. Sequence je specifikován jako BodyCancellationScope aktivity, která je součástí aktivity TryCatch. Výjimka je vyvolána z těla objektu Sequence, je zpracována nadřazenou TryCatch aktivitou a Sequence je zrušena.

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 {e.InstanceId} Terminated.");
        Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
    }
    else
    {
        Console.WriteLine($"Workflow {e.InstanceId} Completed.");
    }
};

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

Při vyvolání tohoto pracovního postupu se v konzole zobrazí následující výstup.

Sekvencování začíná.Sekvence byla zrušena.Došlo k výjimce.Pracovní postup e3c18939-121e-4c43-af1c-ba1ce977ce55 Dokončeno.

Vyvolání výjimek z obslužné rutiny zrušení

Všechny výjimky vyvolané z CancellationHandlerCancellationScope jsou pro pracovní postup fatální. Pokud existuje možnost, že výjimky uniknou z CancellationHandler, použijte TryCatch v CancellationHandler ke zpracování a ošetření těchto výjimek.

Zrušení pomocí funkce CompensableActivity

Stejně jako aktivita CancellationScope, CompensableActivityCancellationHandler. Pokud je zrušena CompensableActivity , jsou vyvolány všechny aktivity v jeho CancellationHandler . To může být užitečné pro zrušení částečně dokončené kompenzovatelné práce. Informace o tom, jak použít CompensableActivity pro kompenzaci a zrušení, naleznete v tématu Kompenzace.

Zrušení pomocí uživatelských aktivit

Autoři vlastních aktivit mohou do vlastních aktivit implementovat logiku zrušení několika různými způsoby. Vlastní aktivity odvozené z Activity můžou implementovat logiku zrušení umístěním aktivity CancellationScope nebo jiné vlastní aktivity, která obsahuje logiku zrušení, do těla aktivity. AsyncCodeActivity a NativeActivity odvozené aktivity mohou přepsat jejich příslušnou Cancel metodu a implementovat tam logiku zrušení. CodeActivity odvozené aktivity nemají poskytovánu žádnou možnost zrušení, protože veškerá jejich práce je vykonávána v jediném okamžiku, když je volána metoda Execute časem runtime. Pokud metoda execute ještě nebyla volána a založená CodeActivity aktivita je zrušena, aktivita se zavře se stavem Canceled a Execute metoda není volána.

Zrušení pomocí NativeActivity

NativeActivity odvozené aktivity mohou přepsat metodu Cancel tak, aby poskytovala vlastní logiku zrušení. Pokud tato metoda není přepsána, použije se výchozí logika zrušení pracovního postupu. Výchozí zrušení je proces, pro který NativeActivity nepřepíše metodu Cancel nebo jehož metoda Cancel volá základní metodu NativeActivityCancel. Když je aktivita zrušena, běhové prostředí označí aktivitu příznakem zrušení a automaticky zpracuje určité úklidové úkoly. Pokud má aktivita pouze nevyřešené záložky, záložky budou odebrány a aktivita bude označena jako Canceled. Všechny nevyřízené podřízené aktivity zrušené aktivity budou také zrušeny. Pokus o naplánování dalších dětských aktivit bude ignorován a aktivita bude označena jako Canceled. Pokud se nějaká nevyřízená podřízená aktivita dokončí v daném Canceled nebo Faulted stavu, označí se tato aktivita jako Canceled. Všimněte si, že žádost o zrušení je možné ignorovat. Pokud aktivita nemá žádné zbývající záložky ani probíhající podřízené aktivity a neplánuje žádné další pracovní položky po označení ke zrušení, dokončí se úspěšně. Toto výchozí zrušení stačí pro mnoho scénářů, ale pokud je potřeba další logika zrušení, můžete použít předdefinované aktivity zrušení nebo vlastní aktivity.

V následujícím příkladu je definováno přepsání aktivity Cancel, které je založeno na NativeActivity pomocí ParallelForEach. Při zrušení aktivity zpracovává toto přepsání logiku jejího zrušení. Tento příklad je součástí ukázky Non-Generic 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 odvozené aktivity mohou určit, zda bylo zrušení požadováno kontrolou vlastnosti IsCancellationRequested, a označit se jako zrušené voláním metody MarkCanceled. Volání MarkCanceled nedokončí aktivitu okamžitě. Modul runtime obvykle dokončí aktivitu, pokud nemá další nevyrovnanou práci, ale pokud se zavolá MarkCanceled, konečný stav bude Canceled místo Closed.

Zrušení pomocí AsyncCodeActivity

AsyncCodeActivity Aktivity založené na mohou také poskytnout vlastní logiku zrušení přepsáním metody Cancel. Pokud tato metoda není přepsána, neprovádí se žádné zpracování zrušení, pokud je aktivita zrušena. V následujícím příkladu je definováno přepsání vlastní aktivity založené na Cancel pomocí AsyncCodeActivity. Když je aktivita zrušena, provede požadované chování zrušení.

// 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 odvozené aktivity mohou určit, zda bylo zrušení požadováno kontrolou vlastnosti IsCancellationRequested, a označit se jako zrušené voláním metody MarkCanceled.