WF(Windows Workflow Foundation)는 워크플로를 호스팅하는 몇 가지 방법을 제공합니다. WorkflowInvoker 메서드 호출인 것처럼 워크플로를 호출하는 간단한 방법을 제공하며 지속성을 사용하지 않는 워크플로에만 사용할 수 있습니다. WorkflowApplication 수명 주기 이벤트, 실행 제어, 책갈피 다시 시작 및 지속성에 대한 알림을 포함하는 워크플로를 실행하기 위한 보다 풍부한 모델을 제공합니다. WorkflowServiceHost 메시징 활동을 지원하며 주로 워크플로 서비스에서 사용됩니다. 이 항목에서는 WorkflowInvoker 및 WorkflowApplication사용하여 호스팅하는 워크플로를 소개합니다. WorkflowServiceHost사용하여 워크플로를 호스팅하는 방법에 대한 자세한 내용은 워크플로 서비스 및 호스팅 워크플로 서비스 개요참조하세요.
WorkflowInvoker 사용
WorkflowInvoker 메서드 호출인 것처럼 워크플로를 실행하기 위한 모델을 제공합니다. WorkflowInvoker사용하여 워크플로를 호출하려면 Invoke 메서드를 호출하고 호출할 워크플로의 워크플로 정의를 전달합니다. 이 예제에서는 WriteLine사용하여 WorkflowInvoker 작업이 호출됩니다.
Activity wf = new WriteLine
{
Text = "Hello World."
};
WorkflowInvoker.Invoke(wf);
WorkflowInvoker사용하여 워크플로를 호출하면 워크플로는 호출 스레드에서 실행되고 Invoke 메서드는 유휴 시간을 포함하여 워크플로가 완료될 때까지 차단됩니다. 워크플로가 완료되어야 하는 제한 시간 간격을 구성하려면 Invoke 매개 변수를 사용하는 TimeSpan 오버로드 중 하나를 사용합니다. 이 예제에서는 서로 다른 두 시간 제한 간격으로 워크플로가 두 번 호출됩니다. 첫 번째 워크플로가 완료되지만 두 번째 워크플로는 완료되지 않습니다.
Activity wf = new Sequence()
{
Activities =
{
new WriteLine()
{
Text = "Before the 1 minute delay."
},
new Delay()
{
Duration = TimeSpan.FromMinutes(1)
},
new WriteLine()
{
Text = "After the 1 minute delay."
}
}
};
// This workflow completes successfully.
WorkflowInvoker.Invoke(wf, TimeSpan.FromMinutes(2));
// This workflow does not complete and a TimeoutException
// is thrown.
try
{
WorkflowInvoker.Invoke(wf, TimeSpan.FromSeconds(30));
}
catch (TimeoutException ex)
{
Console.WriteLine(ex.Message);
}
비고
TimeoutException 시간 제한 간격이 경과하고 실행 중에 워크플로가 유휴 상태가 되는 경우에만 throw됩니다. 워크플로가 유휴 상태가 되지 않으면 완료하는 데 지정된 시간 제한 간격보다 더 오래 걸리는 워크플로가 성공적으로 완료됩니다.
WorkflowInvoker 호출 메서드의 비동기 버전도 제공합니다. 자세한 내용은 InvokeAsync 및 BeginInvoke참조하세요.
워크플로의 입력 인수 설정
데이터는 워크플로의 입력 인수에 매핑되는 인수 이름으로 키가 지정된 입력 매개 변수 사전을 사용하여 워크플로에 전달할 수 있습니다. 이 예제에서는 WriteLine 호출되고 해당 Text 인수의 값은 입력 매개 변수 사전을 사용하여 지정됩니다.
Activity wf = new WriteLine();
Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World.");
WorkflowInvoker.Invoke(wf, inputs);
워크플로의 출력 인수 검색
워크플로의 출력 매개 변수는 Invoke호출에서 반환되는 출력 사전을 사용하여 가져올 수 있습니다. 다음 예제에서는 두 개의 입력 인수와 두 개의 출력 인수가 있는 단일 Divide
작업으로 구성된 워크플로를 호출합니다. 워크플로가 호출되면 인수 이름으로 키가 지정된 각 입력 인수의 값을 포함하는 arguments
사전이 전달됩니다.
Invoke
호출이 반환되면 각 출력 인수는 outputs
사전에 반환되며 인수 이름으로도 키가 지정됩니다.
public sealed class Divide : CodeActivity
{
[RequiredArgument]
public InArgument<int> Dividend { get; set; }
[RequiredArgument]
public InArgument<int> Divisor { get; set; }
public OutArgument<int> Remainder { get; set; }
public OutArgument<int> Result { get; set; }
protected override void Execute(CodeActivityContext context)
{
int quotient = Dividend.Get(context) / Divisor.Get(context);
int remainder = Dividend.Get(context) % Divisor.Get(context);
Result.Set(context, quotient);
Remainder.Set(context, remainder);
}
}
int dividend = 500;
int divisor = 36;
Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);
IDictionary<string, object> outputs =
WorkflowInvoker.Invoke(new Divide(), arguments);
Console.WriteLine($"{dividend} / {divisor} = {outputs["Result"]} Remainder {outputs["Remainder"]}");
워크플로가 ActivityWithResult 또는 CodeActivity<TResult>
같은 Activity<TResult>
파생되고 잘 정의된 Result 출력 인수 외에 출력 인수가 있는 경우 추가 인수를 검색하려면 제네릭이 아닌 Invoke
오버로드를 사용해야 합니다. 이렇게 하려면 Invoke
전달된 워크플로 정의가 Activity형식이어야 합니다. 이 예제에서 Divide
활동은 CodeActivity<int>
에서 파생되지만, 단일 반환 값 대신 인수 사전을 반환하는 제네릭이 아닌 Activity의 오버로드를 사용하기 위해 Invoke
로 선언됩니다.
public sealed class Divide : CodeActivity<int>
{
public InArgument<int> Dividend { get; set; }
public InArgument<int> Divisor { get; set; }
public OutArgument<int> Remainder { get; set; }
protected override int Execute(CodeActivityContext context)
{
int quotient = Dividend.Get(context) / Divisor.Get(context);
int remainder = Dividend.Get(context) % Divisor.Get(context);
Remainder.Set(context, remainder);
return quotient;
}
}
int dividend = 500;
int divisor = 36;
Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);
Activity wf = new Divide();
IDictionary<string, object> outputs =
WorkflowInvoker.Invoke(wf, arguments);
Console.WriteLine($"{dividend} / {divisor} = {outputs["Result"]} Remainder {outputs["Remainder"]}");
WorkflowApplication 사용
WorkflowApplication 워크플로 인스턴스 관리를 위한 다양한 기능 집합을 제공합니다. WorkflowApplication 런타임을 캡슐화하는 실제 WorkflowInstance대한 스레드 안전 프록시 역할을 하며 워크플로 인스턴스를 만들고 로드하고, 일시 중지 및 다시 시작, 종료 및 수명 주기 이벤트 알림을 위한 메서드를 제공합니다. WorkflowApplication 사용하여 워크플로를 실행하려면 WorkflowApplication만들고 원하는 수명 주기 이벤트를 구독하고 워크플로를 시작한 다음 완료될 때까지 기다립니다. 이 예제에서는 WriteLine 작업으로 구성된 워크플로 정의가 생성되고 지정된 워크플로 정의를 사용하여 WorkflowApplication 만들어집니다. Completed 처리되므로 워크플로가 완료되면 호스트에 알림이 표시되고, 워크플로가 Run호출로 시작된 다음, 호스트가 워크플로가 완료될 때까지 기다립니다. 워크플로가 완료되면 다음 예제와 같이 AutoResetEvent 설정되고 호스트 애플리케이션이 실행을 다시 시작할 수 있습니다.
AutoResetEvent syncEvent = new AutoResetEvent(false);
Activity wf = new WriteLine
{
Text = "Hello World."
};
// Create the WorkflowApplication using the desired
// workflow definition.
WorkflowApplication wfApp = new WorkflowApplication(wf);
// Handle the desired lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
syncEvent.Set();
};
// Start the workflow.
wfApp.Run();
// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();
WorkflowApplication 수명 주기 이벤트
Completed외에도 워크플로가 언로드(Unloaded), 중단됨(Aborted), 유휴 상태가 되거나(Idle 및 PersistableIdle) 처리되지 않은 예외가 발생하거나(OnUnhandledException) 호스트 작성자에게 알림을 받을 수 있습니다. 워크플로 애플리케이션 개발자는 다음 예제와 같이 이러한 알림을 처리하고 적절한 조치를 취할 수 있습니다.
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.");
// Outputs can be retrieved from the Outputs dictionary,
// keyed by argument name.
// Console.WriteLine($"The winner is {e.Outputs["Winner"]}.");
}
};
wfApp.Aborted = delegate (WorkflowApplicationAbortedEventArgs e)
{
// Display the exception that caused the workflow
// to abort.
Console.WriteLine($"Workflow {e.InstanceId} Aborted.");
Console.WriteLine($"Exception: {e.Reason.GetType().FullName}\n{e.Reason.Message}");
};
wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
// Perform any processing that should occur
// when a workflow goes idle. If the workflow can persist,
// both Idle and PersistableIdle are called in that order.
Console.WriteLine($"Workflow {e.InstanceId} Idle.");
};
wfApp.PersistableIdle = delegate (WorkflowApplicationIdleEventArgs e)
{
// Instruct the runtime to persist and unload the workflow.
// Choices are None, Persist, and Unload.
return PersistableIdleAction.Unload;
};
wfApp.Unloaded = delegate (WorkflowApplicationEventArgs e)
{
Console.WriteLine($"Workflow {e.InstanceId} Unloaded.");
};
wfApp.OnUnhandledException = delegate (WorkflowApplicationUnhandledExceptionEventArgs e)
{
// Display the unhandled exception.
Console.WriteLine($"OnUnhandledException in Workflow {e.InstanceId}\n{e.UnhandledException.Message}");
Console.WriteLine($"ExceptionSource: {e.ExceptionSource.DisplayName} - {e.ExceptionSourceInstanceId}");
// Instruct the runtime to terminate the workflow.
// Other choices are Abort and Cancel. Terminate
// is the default if no OnUnhandledException handler
// is present.
return UnhandledExceptionAction.Terminate;
};
워크플로의 입력 인수 설정
데이터는 WorkflowInvoker사용할 때 데이터가 전달되는 방식과 유사하게 매개 변수 사전을 사용하여 시작할 때 워크플로로 전달될 수 있습니다. 사전의 각 항목은 지정된 워크플로의 입력 인수에 매핑됩니다. 이 예제에서는 WriteLine 작업으로 구성된 워크플로가 호출되고 해당 Text 인수는 입력 매개 변수 사전을 사용하여 지정됩니다.
AutoResetEvent syncEvent = new AutoResetEvent(false);
Activity wf = new WriteLine();
// Create the dictionary of input parameters.
Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World!");
// Create the WorkflowApplication using the desired
// workflow definition and dictionary of input parameters.
WorkflowApplication wfApp = new WorkflowApplication(wf, inputs);
// Handle the desired lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
syncEvent.Set();
};
// Start the workflow.
wfApp.Run();
// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();
워크플로의 출력 인수 검색
워크플로가 완료되면 Completed 사전에 액세스하여 출력 인수를 WorkflowApplicationCompletedEventArgs.Outputs 처리기에서 검색할 수 있습니다. 다음 예제에서는 WorkflowApplication사용하여 워크플로를 호스트합니다.
WorkflowApplication 인스턴스는 단일 DiceRoll
작업으로 구성된 워크플로 정의를 사용하여 생성됩니다.
DiceRoll
작업에는 주사위 롤 작업의 결과를 나타내는 두 개의 출력 인수가 있습니다. 워크플로가 완료되면 출력이 Completed 처리기에서 검색됩니다.
public sealed class DiceRoll : CodeActivity
{
public OutArgument<int> D1 { get; set; }
public OutArgument<int> D2 { get; set; }
static Random r = new Random();
protected override void Execute(CodeActivityContext context)
{
D1.Set(context, r.Next(1, 7));
D2.Set(context, r.Next(1, 7));
}
}
// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(new DiceRoll());
// 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.");
// Outputs can be retrieved from the Outputs dictionary,
// keyed by argument name.
Console.WriteLine($"The two dice are {e.Outputs["D1"]} and {e.Outputs["D2"]}.");
}
};
// Run the workflow.
wfApp.Run();
비고
WorkflowApplication 및 WorkflowInvoker 입력 인수 사전을 사용하고 out
인수 사전을 반환합니다. 이러한 사전 매개 변수, 속성 및 반환 값은 IDictionary<string, object>
형식입니다. 전달되는 사전 클래스의 실제 인스턴스는 IDictionary<string, object>
구현하는 모든 클래스일 수 있습니다. 이 예제에서는 Dictionary<string, object>
사용됩니다. 사전에 대한 자세한 내용은 IDictionary<TKey,TValue> 및 Dictionary<TKey,TValue>참조하세요.
책갈피를 사용하여 실행 중인 워크플로에 데이터 전달
책갈피는 작업이 다시 시작될 때까지 수동적으로 대기할 수 있는 메커니즘이며 실행 중인 워크플로 인스턴스에 데이터를 전달하는 메커니즘입니다. 활동이 데이터를 기다리는 경우 다음 예제와 같이 Bookmark 만들고 Bookmark 다시 시작될 때 호출할 콜백 메서드를 등록할 수 있습니다.
public sealed class ReadLine : NativeActivity<string>
{
[RequiredArgument]
public InArgument<string> BookmarkName { get; set; }
protected override void Execute(NativeActivityContext context)
{
// Create a Bookmark and wait for it to be resumed.
context.CreateBookmark(BookmarkName.Get(context),
new BookmarkCallback(OnResumeBookmark));
}
// NativeActivity derived activities that do asynchronous operations by calling
// one of the CreateBookmark overloads defined on System.Activities.NativeActivityContext
// must override the CanInduceIdle property and return true.
protected override bool CanInduceIdle
{
get { return true; }
}
public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
{
// When the Bookmark is resumed, assign its value to
// the Result argument.
Result.Set(context, (string)obj);
}
실행되면 ReadLine
작업은 Bookmark만들고 콜백을 등록한 다음 Bookmark 다시 시작될 때까지 기다립니다. 다시 시작되면 ReadLine
활동은 Bookmark 함께 전달된 데이터를 Result 인수에 할당합니다. 이 예제에서는 ReadLine
작업을 사용하여 사용자의 이름을 수집하고 콘솔 창에 표시하는 워크플로를 만듭니다.
Variable<string> name = new Variable<string>();
Activity wf = new Sequence
{
Variables = { name },
Activities =
{
new WriteLine
{
Text = "What is your name?"
},
new ReadLine
{
BookmarkName = "UserName",
Result = new OutArgument<string>(name)
},
new WriteLine
{
Text = new InArgument<string>((env) =>
("Hello, " + name.Get(env)))
}
}
};
// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);
// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);
wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
idleEvent.Set();
};
// Run the workflow.
wfApp.Run();
// Wait for the workflow to go idle before gathering
// the user's input.
idleEvent.WaitOne();
// Gather the user's input and resume the bookmark.
// Bookmark resumption only occurs when the workflow
// is idle. If a call to ResumeBookmark is made and the workflow
// is not idle, ResumeBookmark blocks until the workflow becomes
// idle before resuming the bookmark.
BookmarkResumptionResult result = wfApp.ResumeBookmark("UserName",
Console.ReadLine());
// Possible BookmarkResumptionResult values:
// Success, NotFound, or NotReady
Console.WriteLine($"BookmarkResumptionResult: {result}");
ReadLine
작업이 실행되면 Bookmark 이라는 UserName
를 만든 다음, 책갈피가 재개될 때까지 기다립니다. 호스트는 원하는 데이터를 수집한 다음 Bookmark다시 시작합니다. 워크플로를 다시 시작하고 이름을 표시한 다음 완료합니다.
호스트 애플리케이션은 워크플로를 검사하여 활성 책갈피가 있는지 확인할 수 있습니다. 이 작업은 GetBookmarks 인스턴스의 WorkflowApplication 메서드를 호출하거나 WorkflowApplicationIdleEventArgs 처리기에서 Idle 검사하여 수행할 수 있습니다.
다음 코드 예제는 책갈피가 다시 시작되기 전에 활성 책갈피가 열거된다는 점을 제외하고 이전 예제와 같습니다. 워크플로가 시작되고 Bookmark 만들어지고 워크플로가 유휴 상태가 되면 GetBookmarks 호출됩니다. 워크플로가 완료되면 콘솔에 다음 출력이 표시됩니다.
이름은 무엇인가요?
BookmarkName: UserName - OwnerDisplayName: ReadLineSteve안녕하세요, Steve
Variable<string> name = new Variable<string>();
Activity wf = new Sequence
{
Variables = { name },
Activities =
{
new WriteLine
{
Text = "What is your name?"
},
new ReadLine
{
BookmarkName = "UserName",
Result = new OutArgument<string>(name)
},
new WriteLine
{
Text = new InArgument<string>((env) =>
("Hello, " + name.Get(env)))
}
}
};
// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);
// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);
wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
// You can also inspect the bookmarks from the Idle handler
// using e.Bookmarks
idleEvent.Set();
};
// Run the workflow.
wfApp.Run();
// Wait for the workflow to go idle and give it a chance
// to create the Bookmark.
idleEvent.WaitOne();
// Inspect the bookmarks
foreach (BookmarkInfo info in wfApp.GetBookmarks())
{
Console.WriteLine($"BookmarkName: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
}
// Gather the user's input and resume the bookmark.
wfApp.ResumeBookmark("UserName", Console.ReadLine());
다음 코드 예제에서는 WorkflowApplicationIdleEventArgs 인스턴스의 Idle 처리기에 전달된 WorkflowApplication 검사합니다. 이 예제에서 유휴 상태로 전환되는 워크플로에는 Bookmark이라는 이름의 EnterGuess
이(가) 하나 있으며, ReadInt
라는 이름의 활동에 소유되어 있습니다. 이 코드 예제는 워크플로실행하는 방법을 다루며, 이는 시작하기 튜토리얼의 일부입니다. 해당 단계의 Idle 처리기가 이 예제의 코드를 포함하도록 수정된 경우 다음 출력이 표시됩니다.
BookmarkName: EnterGuess - OwnerDisplayName: ReadInt
wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
foreach (BookmarkInfo info in e.Bookmarks)
{
Console.WriteLine($"BookmarkName: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
}
idleEvent.Set();
};
요약
WorkflowInvoker 워크플로를 호출하는 간단한 방법을 제공하며 워크플로 시작 시 데이터를 전달하고 완료된 워크플로에서 데이터를 추출하는 메서드를 제공하지만 WorkflowApplication 사용할 수 있는 더 복잡한 시나리오는 제공하지 않습니다.
.NET