Udostępnij za pośrednictwem


Modelowanie zachowań anulowania w przepływach pracy

Działania można anulować wewnątrz przepływu pracy, na przykład przez Parallel działanie anulujące niekompletne gałęzie, gdy CompletionCondition oceni się na true, lub spoza przepływu pracy, jeśli host wywoła Cancel metodę. Aby zapewnić obsługę anulowania, autorzy przepływów pracy mogą używać CancellationScope aktywności, CompensableActivity aktywności lub tworzyć niestandardowe aktywności zapewniające logikę anulowania. Ten temat zawiera omówienie anulowania w przepływach pracy.

Anulowanie, odszkodowanie i transakcje

Transakcje dają aplikacji możliwość przerwania (wycofania) wszystkich zmian wykonywanych w ramach transakcji, jeśli wystąpią jakiekolwiek błędy w jakiejkolwiek części procesu transakcji. Jednak nie wszystkie prace, które mogą wymagać anulowania lub cofnięcia, są odpowiednie dla transakcji, takich jak długotrwała praca lub praca, która nie obejmuje zasobów transakcyjnych. Rekompensata zapewnia model umożliwiający cofnięcie wcześniej ukończonej pracy nie transakcyjnej, jeśli w przepływie pracy wystąpi kolejna awaria. Anulowanie udostępnia model dla autorów przepływów pracy i działań do obsługi pracy nie transakcyjnej, która nie została ukończona. Jeśli działanie nie zostało ukończone i zostanie anulowane, jego logika anulowania zostanie wywołana, jeśli jest dostępna.

Uwaga

Aby uzyskać więcej informacji na temat transakcji i odszkodowania, zobacz Transakcje i Rekompensata.

Używanie działania CancellationScope

Aktywność CancellationScope ma dwie sekcje, które mogą zawierać aktywności podrzędne: Body i CancellationHandler. Body to miejsce umieszczania działań tworzących logikę aktywności, a CancellationHandler to miejsce dla działań zapewniających logikę anulowania. Działanie można anulować tylko wtedy, gdy nie zostało ukończone. W przypadku CancellationScope działania ukończenie odnosi się do ukończenia działań w części Body. Jeśli żądanie anulowania zostało zaplanowane, a działania w obiekcie Body nie zostały ukończone, CancellationScope zostanie oznaczone jako Canceled, a działania CancellationHandler zostaną wykonane.

Anulowanie przepływu pracy przez hosta

Host może anulować przepływ pracy, wywołując metodę Cancel wystąpienia WorkflowApplication, które hostuje przepływ pracy. W poniższym przykładzie tworzony jest przepływ pracy z elementem CancellationScope. Przepływ pracy jest wywoływany, a następnie host wywołuje metodę Cancel. Główne wykonanie przepływu pracy zostaje zatrzymane, wywoływany jest CancellationHandlerCancellationScope, a następnie przepływ pracy kończy się statusem 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();

Po wywołaniu tego przepływu pracy następujące dane wyjściowe zostaną wyświetlone w konsoli programu .

Uruchamianie przepływu pracy.
Wywołano procedurę anulowania.Przepływ pracy b30ebb30-df46-4d90-a211-e31c38d8db3c Anulowano.

Uwaga

Po anulowaniu działania i wywołaniu CancellationScope, to do autora przepływu pracy należy określenie, jaki postęp zostało osiągnięte przez anulowane działanie przed jego anulowaniem, aby móc zapewnić odpowiednią logikę anulowania. Element CancellationHandler nie zawiera żadnych informacji o postępie anulowanego działania.

Przepływ pracy można również anulować z hosta, jeśli nieobsługiwany wyjątek dociera ponad katalog główny przepływu pracy, a OnUnhandledException program obsługi zwraca wartość Cancel. W tym przykładzie przepływ pracy zostaje uruchomiony, a następnie zostaje zgłoszony wyjątek ApplicationException. Ten wyjątek nie jest obsługiwany przez przepływ pracy, dlatego wywoływany jest program obsługi OnUnhandledException. Procedura obsługi instruuje środowisko uruchomieniowe, aby anulowało przepływ pracy, a CancellationHandler aktualnie wykonywana CancellationScope aktywność jest wywoływana.

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

Po wywołaniu tego przepływu pracy następujące dane wyjściowe zostaną wyświetlone w konsoli programu .

Uruchamianie przepływu pracy.
OnUnhandledException w przepływie pracy 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9Zgłoszono wyjątek aplikacji.Wywołano procedurę anulowania.Przepływ pracy 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 anulowano.

Anulowanie działania z poziomu przepływu pracy

Działanie można również anulować przez jego element nadrzędny. Jeśli na przykład działanie Parallel ma wiele gałęzi wykonywanych równocześnie, a jego CompletionCondition jest równe true, to jego niekompletne gałęzie zostaną anulowane. W tym przykładzie zostanie utworzone działanie Parallel, które ma dwie gałęzie. Jego CompletionCondition jest ustawione na true, więc Parallel kończy się, gdy tylko któraś z jego gałęzi zostanie ukończona. W tym przykładzie gałąź 2 zostanie ukończona, dlatego gałąź 1 zostanie anulowana.

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

Po wywołaniu tego przepływu pracy następujące dane wyjściowe zostaną wyświetlone w konsoli programu .

Rozpoczyna się gałąź 1.
Gałąź 2 ukończona.Gałąź 1 anulowana.Przepływ pracy e0685e24-18ef-4a47-acf3-5c638732f3be ukończono. Działania są również anulowane, jeśli wyjątek przechodzi przez korzeń działania, ale jest obsłużony na wyższym poziomie w przepływie pracy. W tym przykładzie główna logika przepływu pracy składa się z Sequence aktywności. Parametr Sequence jest określony jako Body działania typu CancellationScope, które jest zawarte w działaniu typu TryCatch. Wyjątek jest zgłaszany z treści Sequenceobiektu , jest obsługiwany przez działanie nadrzędne TryCatch , a element Sequence jest anulowany.

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

Po wywołaniu tego przepływu pracy następujące dane wyjściowe zostaną wyświetlone w konsoli programu .

Uruchamianie sekwencji.
Anulowano sekwencję.Wystąpił wyjątek.Przepływ pracy e3c18939-121e-4c43-af1c-ba1ce977ce55 został ukończony.

Zgłaszanie wyjątków z procedury obsługi anulowania

Wszelkie wyjątki zgłaszane z CancellationHandler elementu CancellationScope są krytyczne dla przepływu pracy. Jeśli istnieje możliwość wyjątków wychodzących z CancellationHandler, użyj TryCatch w CancellationHandler , aby przechwycić i obsłużyć te wyjątki.

Anulowanie przy użyciu funkcji CompensableActivity

Podobnie jak działanie CancellationScope , element CompensableActivity ma wartość CancellationHandler. Jeśli element CompensableActivity zostanie anulowany, wszystkie działania w jego CancellationHandler są wywoływane. Może to być przydatne do cofnięcia częściowo wykonanej pracy wymagającej wynagrodzenia. Aby uzyskać informacje o sposobie używania CompensableActivity do rekompensaty i anulowania, zobacz Rekompensata.

Anulowanie przy użyciu działań niestandardowych

Autorzy działań niestandardowych mogą implementować logikę anulowania w swoich niestandardowych działaniach na kilka różnych sposobów. Działania niestandardowe wywodzące się z Activity mogą implementować logikę anulowania poprzez umieszczenie CancellationScope lub innego niestandardowego działania zawierającego logikę anulowania w treści działania. AsyncCodeActivity i NativeActivity działania pochodne mogą zastąpić odpowiednie metody Cancel i zapewnić tam logikę anulowania. CodeActivity Działania pochodne nie zapewniają możliwości anulowania, ponieważ cała ich praca jest wykonywana w jednym cyklu wykonywania, gdy środowisko uruchomieniowe wywołuje metodę Execute. Jeśli metoda execute nie została jeszcze wywołana i aktywność oparta na CodeActivity została anulowana, aktywność zostaje zamknięta ze stanem Canceled, a metoda Execute nie zostaje wywołana.

Anulowanie przy użyciu elementu NativeActivity

NativeActivity Działania pochodne mogą zastąpić metodę Cancel w celu zapewnienia niestandardowej logiki anulowania. Jeśli ta metoda nie zostanie zastąpiona, zostanie zastosowana domyślna logika anulowania przepływu pracy. Domyślne anulowanie to proces, który występuje dla elementu NativeActivity, który nie zastępuje metody Cancel lub którego metoda Cancel wywołuje metodę podstawową NativeActivityCancel. Po anulowaniu działania, system wykonawczy oznacza działanie do anulowania i automatycznie obsługuje pewne porządkowanie. Jeśli działanie zawiera tylko zaległe zakładki, zakładki zostaną usunięte, a działanie zostanie oznaczone jako Canceled. Wszystkie nierozstrzygnięte działania podrzędne anulowanego działania zostaną również anulowane. Każda próba zaplanowania dodatkowych aktywności dla dzieci spowoduje zignorowanie próby i zostanie oznaczone jako Canceled. Jeśli jakiekolwiek zaległe działanie podrzędne zakończy się w stanie Canceled lub Faulted, to działanie zostanie oznaczone jako Canceled. Należy pamiętać, że żądanie anulowania można zignorować. Jeśli działanie nie ma żadnych zaległych zakładek ani wykonywanych działań podrzędnych i nie zaplanowano żadnych dodatkowych zadań roboczych po oznaczeniu do anulowania, zakończy się bez problemów. To domyślne anulowanie jest wystarczające w wielu scenariuszach, ale jeśli jest wymagana dodatkowa logika anulowania, można użyć wbudowanych działań anulowania lub niestandardowych działań.

W poniższym przykładzie zdefiniowano przesłonięcie opartej na Cancel niestandardowej czynności NativeActivity. Po anulowaniu działania to zastąpienie obsługuje logikę anulowania działania. Ten przykład jest częścią przykładu 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 Działania pochodne mogą określić, czy zostało zażądane anulowanie, poprzez sprawdzenie właściwości IsCancellationRequested i oznaczenie się jako anulowane poprzez wywołanie metody MarkCanceled. Wywołanie MarkCanceled nie powoduje natychmiastowego ukończenia działania. Jak zwykle środowisko uruchomieniowe zakończy działanie, gdy nie ma więcej zaległej pracy, ale jeśli MarkCanceled jest wywoływany, stan końcowy będzie Canceled zamiast Closed.

Anulowanie przy użyciu funkcji AsyncCodeActivity

AsyncCodeActivity Działania oparte na mogą również udostępniać niestandardową logikę anulowania, poprzez przesłanianie metody Cancel. Jeśli ta metoda nie zostanie zastąpiona, nie zostanie wykonana żadna obsługa anulowania, jeśli działanie zostanie anulowane. W poniższym przykładzie zdefiniowano przesłonięcie niestandardowej aktywności opartej na Cancel, oznaczonej jako AsyncCodeActivityExecutePowerShell. Po anulowaniu aktywności wykonuje pożądane zachowanie anulowania.

// 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 Działania pochodne mogą określić, czy zostało zażądane anulowanie, poprzez sprawdzenie właściwości IsCancellationRequested i oznaczenie się jako anulowane poprzez wywołanie metody MarkCanceled.