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