Использование WorkflowInvoker и WorkflowApplication
Данный раздел относится к версии Windows Workflow Foundation 4.
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("{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, обратившись к словарю System.Activities.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 о словарях см. в разделах Dictionary и . |
Передача данных в запущенный рабочий процесс при помощи закладок
Закладки — это механизм, при помощи которого действие может пассивно ждать возобновления; с их помощью можно передавать данные в запущенный экземпляр рабочего процесса. Если действие ждет данные, оно может создать 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
Стив
Привет, Стив
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.