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


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

Данный раздел относится к версии Windows Workflow Foundation 4.

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

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

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

Ff407124.note(ru-ru,VS.100).gifПримечание
Дополнительные сведения о транзакциях и компенсации см. в разделах Транзакции рабочих процессов и Компенсация.

Использование 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 {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();

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

Starting the workflow.
				
CancellationHandler invoked.
Workflow b30ebb30-df46-4d90-a211-e31c38d8db3c Canceled.
Ff407124.note(ru-ru,VS.100).gifПримечание
Если отменяется действие 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 {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();

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

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

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

Действие также может быть отменено его родительским действием. Например, если действие 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 {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();

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

Branch 1 starting.
				
Branch 2 complete.
Ветвь 1 отменена.
Workflow e0685e24-18ef-4a47-acf3-5c638732f3be Completed.

Действия также отменяются, если исключение «всплывает» вслед за прохождением корня действия, но обрабатывается на более высоком уровне в рабочем процессе. В этом примере основная логика рабочего процесса состоит из действия 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 {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();

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

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

Формирование исключений из 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 которого вызывает базовый метод NativeActivity Cancel. При отмене действия среда выполнения отмечает действие как подлежащее отмене и автоматически производит определенную очистку. Если действие имеет только закладки, ожидающие обработки, то закладки удаляются и действие отмечается как Canceled. Любые ожидающие обработки дочерние действия отмененного действия, в свою очередь, будут отменены. Любая попытка запланировать дополнительные дочерние действия будет проигнорирована, и действие будет отмечено как Canceled. Если любое ожидающее обработки дочернее действие завершится в состоянии Canceled или Faulted, то действие будет отмечено как Canceled. Следует отметить, что запрос отмены может быть проигнорирован. Если действие не имеет ожидающих обработки закладок или выполняющихся дочерних действий, а также не планирует никаких дополнительных элементов работы после того, как оно было отмечено для отмены, то оно завершится успешно. Эта предусмотренная по умолчанию логика отмены является приемлемой для многих сценариев, но если потребуется дополнительная логика отмены, то могут использоваться встроенные действия отмены или пользовательские действия.

В следующем примере метод Cancel переопределяет пользовательское действие ParallelForEach, основанное на действии NativeActivity. Если действие отменяется, это переопределение отрабатывает логику отмены для действия. Этот пример является частью образца Нестандартное действие 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. Если этот метод не переопределен, то при отмене действия не выполняется никакая обработка отмены. В следующем примере определяется переопределение пользовательского действия ExecutePowerShell, основанного на действии AsyncCodeActivity методом Cancel. Если действие отменяется, оно реализует желаемое поведение отмены. Этот пример является частью образца Использование действия InvokePowerShell.

// 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.