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

Windows Workflow Foundation (WF) предоставляет несколько методов размещения рабочих процессов. WorkflowInvoker предоставляет простой способ вызова рабочего процесса аналогично вызову метода и может использоваться только для рабочих процессов, не использующих сохраняемость. WorkflowApplication предоставляет улучшенную модель выполнения рабочих процессов, которая обеспечивает уведомления о событиях жизненного цикла, управление выполнением, возобновление закладок и сохраняемость. WorkflowServiceHost предоставляет поддержку для действий по обмену сообщениями и главным образом используется со службами Workflow Service. В этом разделе вы познакомитесь с размещением рабочих процессов в 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 создается только в случае, если время ожидания истекло и рабочий процесс перешел в состояние бездействия во время выполнения. Рабочий процесс, не завершающийся в течение отведенного времени ожидания, завершается успешно, если не переходит в состояние простоя.

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("{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), переходит в состояние бездействия (Idle и PersistableIdle) или когда происходит необработанное исключение (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();

Примечание.

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: {0}", result);

При выполнении действие ReadLine создает закладку Bookmark с именем UserName и ждет возобновления чтения с этой закладки. Узел собирает необходимые данные и возобновляет чтение с закладки Bookmark. Рабочий процесс возобновляется, отображает имя и затем завершается.

Ведущее приложение может проверять рабочий процесс на наличие активных закладок. Это осуществляется вызовом метода GetBookmarks экземпляра WorkflowApplication или проверкой WorkflowApplicationIdleEventArgs в обработчике Idle.

Следующий пример кода подобен предыдущему примеру, за исключением того, что активные закладки перечисляются до возобновления закладки. Рабочий процесс запускается, и, когда создается Bookmark, а рабочий процесс переходит в состояние бездействия, вызывается метод GetBookmarks. После завершения рабочего процесса на консоль выводятся следующие данные.

Как вас зовут?
BookmarkName: UserName — OwnerDisplayName: ReadLineСтив Hello, Стив

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.