워크플로의 취소 동작 모델링
워크플로 내에서 활동을 취소할 수도 있고(예: 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 {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();
이 워크플로가 호출되면 다음 출력이 콘솔에 표시됩니다.
워크플로를 시작합니다.
CancellationHandler가 호출되었습니다.워크플로 b30ebb30-df46-4d90-a211-e31c38d8db3c가 취소되었습니다.
참고 항목
CancellationScope 활동이 취소되고 CancellationHandler가 호출되는 경우 적절한 취소 논리를 제공하기 위해서는 해당 활동을 취소하기 전에 취소 대상 활동이 얼마나 진행되었는지 워크플로 작성자가 직접 확인해야 합니다. CancellationHandler에서는 취소된 활동의 진행률에 대한 어떠한 정보도 제공하지 않습니다.
워크플로의 루트를 지난 뒤에 처리되지 않은 예외가 버블링되고 OnUnhandledException 처리기가 Cancel을 반환하는 경우에도 호스트에서 워크플로를 취소할 수 있습니다. 이 예제에서는 워크플로가 시작된 후 ApplicationException을 throw합니다. 이 예외는 워크플로에서 처리되지 않으므로 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();
이 워크플로가 호출되면 다음 출력이 콘솔에 표시됩니다.
워크플로를 시작합니다.
워크플로 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9의 OnUnhandledExceptionApplicationException이 throw되었습니다.CancellationHandler가 호출되었습니다.워크플로 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 {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();
이 워크플로가 호출되면 다음 출력이 콘솔에 표시됩니다.
분기 1이 시작됩니다.
분기 2가 완료되었습니다.분기 1이 취소되었습니다.워크플로 e0685e24-18ef-4a47-acf3-5c638732f3be가 완료되었습니다. 활동은 예외가 활동의 루트를 지나서 버블링되지만 워크플로에서 더 높은 수준에서 처리되는 경우에도 취소됩니다. 이 예제에서 워크플로의 주 논리는 Sequence 활동으로 구성되어 있습니다. Sequence는 Body 활동에 포함된 CancellationScope 활동의 TryCatch로 지정됩니다. Sequence의 본문에서 예외가 throw되면 부모 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();
이 워크플로가 호출되면 다음 출력이 콘솔에 표시됩니다.
시퀀스가 시작됩니다.
시퀀스가 취소되었습니다.예외가 catch되었습니다.워크플로 e3c18939-121e-4c43-af1c-ba1ce977ce55가 완료되었습니다.
CancellationHandler에서 예외 throw
CancellationHandler의 CancellationScope에서 throw되는 예외는 워크플로에 치명적인 영향을 줍니다. CancellationHandler에서 예외가 이스케이프될 가능성이 있으면 TryCatch에 CancellationHandler를 사용하여 해당 예외를 catch 및 처리해야 합니다.
CompensableActivity를 사용한 취소
CancellationScope 활동과 마찬가지로 CompensableActivity에도 CancellationHandler가 있습니다. CompensableActivity를 취소하면 해당 CancellationHandler의 모든 활동이 호출됩니다. 이는 보정 가능하며 부분적으로 완료된 작업을 실행 취소하는 데 유용할 수 있습니다. 보상 및 취소에 대해 CompensableActivity를 사용하는 방법에 대한 자세한 내용은 보상을 참조하세요.
사용자 지정 활동을 사용한 취소
사용자 지정 활동 작성자는 각기 다른 여러 가지 방법으로 사용자 지정 활동에 취소 논리를 구현할 수 있습니다. Activity에서 파생되는 사용자 지정 활동은 CancellationScope 또는 활동 본문에 취소 논리를 포함하는 다른 사용자 지정 활동을 배치하여 취소 논리를 구현할 수 있습니다. AsyncCodeActivity 및 NativeActivity 파생 활동은 해당 Cancel 메서드를 재정의하고 취소 논리를 제공할 수 있습니다. CodeActivity 파생 활동은 런타임에서 Execute 메서드를 호출할 때 한 번 실행으로 모든 작업을 수행하기 때문에 취소를 위한 수단을 제공하지 않습니다. 실행 메서드가 아직 호출되지 않은 상태에서 CodeActivity 기반 활동이 취소되는 경우 활동이 Canceled 상태로 닫히고 Execute 메서드는 호출되지 않습니다.
NativeActivity를 사용한 취소
NativeActivity 파생 활동에서는 Cancel 메서드를 재정의하여 사용자 지정 취소 논리를 제공할 수 있습니다. 이 메서드를 재정의하지 않으면 기본 워크플로 취소 논리가 적용됩니다. 기본 취소는 Cancel 메서드를 재정의하지 않거나 해당 Cancel 메서드가 기본 NativeActivity Cancel 메서드를 호출하는 NativeActivity에 대해 이루어지는 프로세스입니다. 활동을 취소하면 런타임에 취소 대상 활동에 대한 플래그가 설정되고 특정 정리 작업이 자동으로 처리됩니다. 활동에서 처리 중인 책갈피만 있으면 책갈피가 제거되고 활동이 Canceled로 표시됩니다. 취소된 활동에서 처리 중인 자식 활동이 있으면 자식 활동이 차례로 취소됩니다. 자식 활동을 추가로 예약하려는 시도가 있으면 해당 시도가 무시되고 활동이 Canceled로 표시됩니다. 처리 중이던 자식 활동이 Canceled 또는 Faulted 상태로 완료되면 활동이 Canceled로 표시됩니다. 취소 요청은 무시할 수 없습니다. 활동에서 처리 중인 책갈피나 실행 중인 자식 활동이 전혀 없고 취소 플래그가 설정된 후에 작업 항목을 추가로 예약하지도 않는다면 활동이 성공적으로 완료됩니다. 대부분의 시나리오에는 이와 같은 기본 취소 프로세스로도 충분합니다. 그러나 취소 논리가 추가로 필요한 경우에는 기본 제공 취소 활동이나 사용자 지정 활동을 사용할 수 있습니다.
다음 예제에서는 Cancel의 NativeActivity 재정의를 기반으로 하는 사용자 지정 ParallelForEach
활동을 정의합니다. 활동을 취소하면 이 재정의를 통해 활동의 취소 논리가 처리됩니다. 이 예제는 비제네릭 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 메서드를 호출하여 자신을 취소된 활동으로 표시할 수 있습니다.
.NET