Поделиться через


Моделирование отмены действий в рабочих процессах

Действия можно отменять внутри рабочего процесса, например, действием Parallel, отменяющим неполные ветви, если его условие CompletionCondition возвращает значение true, или извне рабочего процесса, если хост вызывает метод Cancel. Чтобы предусмотреть выполнение отмены, разработчики рабочего процесса могут использовать действие CancellationScope, действие CompensableActivity или создать пользовательские действия, которые предоставляют логику отмены. В этом разделе приведены общие сведения об отмене в рабочих процессах.

Отмена, компенсация и транзакции

Транзакции предоставляют приложению способность прерывать (производить откат) все изменения, выполняемые в пределах транзакции, если в ходе выполнения любой части транзакции возникают какие-либо ошибки. Однако не вся работа, которая может потребовать отмены, подходит для транзакций. К примерам такой работы относятся продолжительные задания или операции, в которых не задействуются ресурсы транзакций. Компенсация предоставляет модель для отмены выполненной ранее работы, которая не входит в состав транзакции, если в последующем эти действия вызвали ошибку в рабочем процессе. Отмена предоставляет разработчикам рабочих процессов и действий модель для обработки незавершенной работы, которая не входит в состав транзакции. Если какое-либо действие отменяется до завершения его выполнения, то будет вызвана логика его отмены при ее наличии.

Примечание.

Дополнительные сведения о транзакциях и компенсации см. в разделе "Транзакции и компенсация".

Использование CancellationScope

Действие CancellationScope имеет два раздела, которые могут содержать дочерние действия: Body и CancellationHandler. В раздел Body помещаются действия, составляющие логику действия, а в раздел CancellationHandler - действия, которые обеспечивают логику отмены действия. Отменено может быть только незавершенное действие. Если речь идет о действии CancellationScope, то под завершением подразумевается завершение действий в Body. Если запрос отмены запланирован и действия в Body не завершены, то область CancellationScope будет помечена как Canceled, и затем будут выполнены действия CancellationHandler.

Прерывание процесса с сервера

Хост может отменить рабочий процесс, вызвав метод Cancel экземпляра WorkflowApplication, хостинга рабочего процесса. В следующем примере создается рабочий процесс, который имеет CancellationScope. Происходит вызов рабочего процесса, а затем хост делает вызов Cancel. Основное выполнение рабочего процесса останавливается, вызывается обработчик CancellationHandler области CancellationScope, после чего рабочий процесс завершается с указанием состояния 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();

При вызове этого рабочего процесса на консоль выводятся следующие данные.

Запуск рабочего процесса.
Вызывается CancellationHandler.Рабочий процесс b30ebb30-df46-4d90-a211-e31c38d8db3c отменен.

Примечание.

Если отменяется действие CancellationScope и CancellationHandler вызывается, то разработчик рабочего процесса несет ответственность за определение того, какая часть отмененного действия была выполнена до его отмены, чтобы предоставить соответствующую логику отмены. CancellationHandler не предоставляет никаких сведений о прогрессе отмененной активности.

Рабочий процесс также может быть отменён с хоста, если за корнем рабочего процесса «всплывает» необработанное исключение и обработчик OnUnhandledException возвращает Cancel. В этом примере рабочий процесс запускается, а затем выбрасывается исключение ApplicationException. Рабочий процесс не обрабатывает это исключение, поэтому вызывается обработчик OnUnhandledException. Обработчик дает команду среде выполнения отменить рабочий процесс, и вызывается CancellationHandler из текущего выполняемого действия CancellationScope.

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

При вызове этого рабочего процесса на консоль выводятся следующие данные.

Запуск рабочего процесса.
OnUnhandledException в рабочем процессе 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9Было вызвано исключение ApplicationException.Вызыван обработчик отмены.Рабочий процесс 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 отменён.

Отмена действия из рабочего процесса

Действие также может быть отменено его родителем. Например, если действие Parallel имеет несколько выполняющихся ветвей и его условие CompletionCondition принимает значение true, то его незавершенные ветви будут отменены. В этом примере создается действие Parallel, которое имеет две ветви. Значение параметра CompletionCondition установлено на true, поэтому Parallel завершается сразу после завершения любой из его ветвей. В этом примере завершается ветвь 2, поэтому ветвь 1 отменяется.

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

При вызове этого рабочего процесса на консоль выводятся следующие данные.

Ветвь 1 начинается.
Ветвь 2 завершена.Ветвь 1 отменена.Рабочий процесс e0685e24-18ef-4a47-acf3-5c638732f3be завершен. Действия также отменяются, если исключение поднимается выше корня действия, но обрабатывается на более высоком уровне рабочего процесса. В этом примере основная логика рабочего процесса состоит из действия Sequence. Последовательность Sequence задается в качестве Body действия CancellationScope, входящего в действие TryCatch. Из текста последовательности Sequence формируется исключение, обрабатывается родительским действием TryCatch, после чего последовательность Sequence отменяется.

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

При вызове этого рабочего процесса на консоль выводятся следующие данные.

Запуск последовательности.
Последовательность отменена.Исключение поймано.Рабочий процесс e3c18939-121e-4c43-af1c-ba1ce977ce55 завершен.

Генерация исключений из CancellationHandler

Исключения, выброшенные из CancellationHandler в CancellationScope, приводят к критическому завершению рабочего процесса. Если есть возможность выхода исключений из CancellationHandler, то используйте TryCatch в CancellationHandler для перехвата и обработки этих исключений.

Отмена с помощью CompensableActivity

Как и действие CancellationScope, действие CompensableActivity имеет CancellationHandler. Если CompensableActivity отменяется, то вызываются любые активности в его CancellationHandler. Это может применяться для отмены частично завершенной работы, которую можно компенсировать. Сведения о том, как использовать CompensableActivity для компенсации и отмены, см. в разделе "Компенсация".

Отмена с использованием настраиваемых действий

Разработчики пользовательских действий могут реализовать логику отмены в своих пользовательских действиях несколькими разными способами. Пользовательские действия, которые являются производными от Activity, могут реализовывать логику отмены путем размещения действия CancellationScope или другого пользовательского действия, содержащего логику отмены, в тексте этого действия. Действия, производные от AsyncCodeActivity и NativeActivity, могут переопределить свои соответствующие методы Cancel и реализовать там логику отмены. Действия, производные от CodeActivity, не обеспечивают возможности отмены, так как вся их работа выполняется за один заход, когда среда выполнения вызывает метод Execute. Если метод выполнения еще не был вызван и действие, основанное на действии CodeActivity, отменяется, действие закрывается с состоянием Canceled, а метод Execute не вызывается.

Отмена с помощью NativeActivity

Производные действия NativeActivity могут переопределять метод Cancel для реализации пользовательской логики отмены. Если этот метод не переопределен, то применяется предусмотренная по умолчанию логика отмены рабочего процесса. Отмена по умолчанию — это процесс, который происходит для NativeActivity, который не переопределяет Cancel метод или чей метод Cancel вызывает базовый NativeActivityCancel метод. При отмене действия среда выполнения отмечает действие как подлежащее отмене и автоматически производит определенную очистку. Если действие имеет только закладки, ожидающие обработки, то закладки удаляются и действие отмечается как Canceled. Любые невыполненные подчиненные активности отмененной активности, в свою очередь, будут отменены. Любая попытка запланировать дополнительные мероприятия для детей будет проигнорирована, и мероприятие будет отмечено как Canceled. Если любое ожидающее завершения дочернее действие завершится в состоянии Canceled или Faulted, то действие будет отмечено как Canceled. Следует отметить, что запрос отмены может быть проигнорирован. Если действие не имеет незавершённых закладок или выполняемых дочерних действий, а также не планирует никаких дополнительных элементов работы после того, как оно было отмечено на отмену, то оно завершится успешно. Эта предусмотренная по умолчанию логика отмены является приемлемой для многих сценариев, но если потребуется дополнительная логика отмены, то могут использоваться встроенные действия отмены или пользовательские действия.

В следующем примере метод Cancel переопределяет пользовательское действие NativeActivity, основанное на действии ParallelForEach. Когда активность отменяется, это переопределение обрабатывает логику отмены для активности. Этот пример является частью примера 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 могут определять, запрошена ли отмена, проверяя значение свойства IsCancellationRequested, и помечать себя как отмененные действия, вызывая метод MarkCanceled. Вызов метода MarkCanceled не приводит к немедленному завершению действия. Как обычно, среда выполнения завершит действие, когда у него не останется больше ожидающей выполнения работы, но если вызывается метод MarkCanceled, то конечным состоянием будет Canceled, а не Closed.

Отмена с помощью AsyncCodeActivity

Деятельность на основе AsyncCodeActivity также может предоставлять пользовательскую логику отмены, переопределяя метод Cancel. Если этот метод не переопределен, то при отмене действия не выполняется никакая обработка отмены. В следующем примере определяется переопределение Cancel для пользовательской активности AsyncCodeActivity, основанной на ExecutePowerShell. Если действие отменяется, оно реализует желаемое поведение отмены.

// 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 могут определять, запрошена ли отмена, проверяя значение свойства IsCancellationRequested, и помечать себя как отмененные действия, вызывая метод MarkCanceled.