WorkflowInvoker 및 WorkflowApplication 사용

WF(Windows Workflow Foundation)는 워크플로를 호스팅하는 몇 가지 방법을 제공합니다. WorkflowInvoker 는 메서드 호출과 같은 방식으로 워크플로를 호출하기 위한 간단한 방법을 제공하며, 지속성을 사용하지 않는 워크플로에만 사용될 수 있습니다. WorkflowApplication은 수명 주기 이벤트 알림, 실행 제어, 책갈피 다시 시작 및 지속성을 비롯한 다양한 워크플로 실행 모델을 제공합니다. WorkflowServiceHost는 메시징 활동에 대한 지원을 제공하며 주로 워크플로 서비스에 사용됩니다. 이 항목에서는 WorkflowInvokerWorkflowApplication을 사용하는 워크플로 호스팅을 소개합니다. 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는 호출 메서드의 비동기 버전도 제공합니다. 자세한 내용은 InvokeAsyncBeginInvoke를 참조하세요.

워크플로의 입력 인수 설정

워크플로의 입력 인수에 매핑되는 인수 이름으로 키가 지정된 입력 매개 변수 사전을 사용하여 워크플로에 데이터를 전달할 수 있습니다. 이 예제에서는 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("{0} / {1} = {2} Remainder {3}",
    dividend, divisor, outputs["Result"], 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("{0} / {1} = {2} Remainder {3}",
    dividend, divisor, outputs["Result"], 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), 유휴 상태가 되거나(IdlePersistableIdle), 처리되지 않은 예외가 발생하면(OnUnhandledException) 호스트 작성자에게 알릴 수 있습니다. 다음 예와 같이 워크플로 애플리케이션 개발자는 이 알림을 처리하고 적절한 작업을 수행할 수 있습니다.

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

        // Outputs can be retrieved from the Outputs dictionary,
        // keyed by argument name.
        // Console.WriteLine("The winner is {0}.", e.Outputs["Winner"]);
    }
};

wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
{
    // Display the exception that caused the workflow
    // to abort.
    Console.WriteLine("Workflow {0} Aborted.", e.InstanceId);
    Console.WriteLine("Exception: {0}\n{1}",
        e.Reason.GetType().FullName,
        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 {0} Idle.", e.InstanceId);
};

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 {0} Unloaded.", e.InstanceId);
};

wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
    // Display the unhandled exception.
    Console.WriteLine("OnUnhandledException in Workflow {0}\n{1}",
        e.InstanceId, e.UnhandledException.Message);

    Console.WriteLine("ExceptionSource: {0} - {1}",
        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 {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);

         // Outputs can be retrieved from the Outputs dictionary,
         // keyed by argument name.
         Console.WriteLine("The two dice are {0} and {1}.",
             e.Outputs["D1"], e.Outputs["D2"]);
     }
 };

// Run the workflow.
 wfApp.Run();

참고

WorkflowApplicationWorkflowInvoker는 입력 인수의 사전을 취하여 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: {0}", result);

ReadLine 활동이 실행되면 Bookmark이라는 UserName를 만든 다음 책갈피가 다시 시작될 때까지 기다립니다. 호스트는 원하는 데이터를 수집한 다음 Bookmark를 다시 시작합니다. 워크플로가 다시 시작되고 이름을 표시한 다음 완료됩니다.

호스트 애플리케이션에서 활성 책갈피가 있는지 여부를 확인하기 위해 워크플로를 조사할 수 있습니다. 이를 위해 GetBookmarks 인스턴스의 WorkflowApplication 메서드를 호출하거나 WorkflowApplicationIdleEventArgs 처리기에서 Idle를 조사할 수 있습니다.

다음 코드 예제는 책갈피를 다시 시작하기 전에 활성 책갈피를 열거한다는 점을 제외하고 앞의 예제와 유사합니다. 워크플로가 시작되고 Bookmark가 만들어지고 워크플로가 유휴 상태가 되면 GetBookmarks가 호출됩니다. 워크플로가 완료되면 다음 출력이 콘솔에 표시됩니다.

이름은 무엇인가요?
BookmarkName: UserName - OwnerDisplayName: ReadLineSteveHello, 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: {0} - OwnerDisplayName: {1}",
        info.BookmarkName, 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: {0} - OwnerDisplayName: {1}",
            info.BookmarkName, info.OwnerDisplayName);
    }

    idleEvent.Set();
};

요약

WorkflowInvoker는 워크플로를 호출하는 간단한 방법과 워크플로를 시작할 때 데이터를 전달하고 완료된 워크플로에서 데이터를 추출하는 메서드를 제공하지만, WorkflowApplication을 사용할 수 있는 복잡한 시나리오는 제공하지 않습니다.