Обзор рабочего потока Windows Workflow. Часть 1Источник: rsdn
Мы представим обзор рабочего потока Windows Workflow (далее - WF), являющийся моделью, в которой вы можете определять и выполнять процессы с использованием набора строительных блоков - действий (activities). WF предлагает визуальный конструктор, который по умолчанию развернут в среде Visual Studio и позволяет перетаскивать действия из панели инструментов на поверхность конструктора, создавая шаблон рабочего потока. Этот шаблон затем может быть выполнен за счет создания экземпляра рабочего потока WorkflowInstance и запуска этого экземпляра. Код, выполняющий рабочий поток, известен как исполняющая среда рабочего потока - WorkflowRuntime, и этот объект также может содержать в себе множество служб, к которым может обращаться рабочий поток. В любое время может одновременно выполняться несколько экземпляров рабочего потока, и исполняющая среда занимается планированием этих экземпляров, сохраняя и восстанавливая их состояние; она также может записывать поведение каждого экземпляра рабочего потока по мере его работы. Рабочий поток конструируется из ряда действий, и эти действия осуществляются в исполняющей среде. Действием может быть отправка электронной почты, обновление строки в базе данных либо выполнение транзакции в системе заднего плана. Существует ряд встроенных действий, которые могут быть использованы для выполнения работ общего назначения, и, кроме того, при необходимости вы можете создавать собственные действия и подключать их к рабочему потоку. Эта глава посвящена обсуждению различных типов рабочих потоков, которые доступны в готовом виде, а также описанию некоторых встроенных действий, доступных в WF. Вы увидите, как работать с некоторыми из стандартных типов действий, а также создадите два собственных действия, чтобы ознакомиться с принципом расширения WF. Мы начнем с канонического примера, с которым имеет дело каждый, кто впервые сталкивается с новой технологией, - "Hello World", - а также опишем все, что вам понадобится для получения исполняющегося рабочего потока на вашей машине разработчика. Пример "Hello World"Чтобы начать работу с WF, вам понадобится загрузить как Windows .NET Framework 3.0, так и Visual Studio 2005 Extensions for Windows Workflow Foundation. Первое загружать не нужно, если вы работаете в среде Windows Vista, поскольку .NET Framework включен в стандартную поставку этой операционной системы. Visual Studio 2005 Extensions for Windows Workflow Foundation - это установочный пакет, добавляющий поддержку рабочих потоков в Visual Studio, и подробности его загрузки вы найдете в конце настоящей главы. После того, как все это будет инсталлировано, вы увидите новый набор типов свойств в узле Workflow (Рабочий поток) внутри диалога New Project (Новый проект), как показано на рис. 41.1.
Выберите из доступных шаблонов Sequential Workflow Console Application (это создаст консольное приложение, которое будет служить хостом для исполняющей среды рабочего потока) и рабочий поток по умолчанию, на который вы сможете потом перетаскивать действия. Затем перетащите действие Code из панели инструментов на поверхность конструктора - так, чтобы получить рабочий поток вроде того, что показан на рис. 41.2.
Восклицательный знак в верхнем правом углу действия указывает на то, что обязательное свойство действия еще не было определено. В данном случае это свойство ExecuteCode, указывающее метод, который будет вызван при выполнении действия. О пометке вашего собственного свойства как обязательного вы узнаете в разделе, посвященном верификации действия. В результате выполнения двойного щелчка на действии кода будет создан нужный метод в отделенном классе, и в нем вы можете использовать Console.Writeline для вывода строки "Hello World", как показано в приведенном ниже фрагменте кода.
Если вы затем соберете и запустите приложение, то увидите текстовый вывод на консоли. Когда программа выполняется, создается экземпляр WorkflowRuntime, а затем конструируется и выполняется экземпляр вашего рабочего потока. Когда активизируется код действия, он вызывает определенный нами метод, который выводит строку на консоль. В разделе "Исполняющая среда рабочего потока" мы опишем в деталях, как организовать хост для исполняющей системы. ДействияВсе, что происходит в рабочем потоке - это действия. Сам рабочий поток представляет собой действие, причем действие специфического типа, которое обычно позволяет определять внутри него другие действия; это известно как составное действие (composite activity), и позднее в главе будут даны примеры других составных действий. Действие - это просто класс, который обязательно наследуется от класса Activity. Класс Activity определяет ряд перегружаемых методов и, вероятно, наиболее важный из них - метод Execute, представленный в следующем фрагменте:
Когда исполняющая система планирует действие к выполнению, обязательно вызывается метод Execute, и именно здесь вы имеет возможность поместить собственный код, реализующий поведение вашего действия. В примере из предыдущего раздела, когда исполняющая система рабочего потока вызывает Execute из CodeActivity, то реализация этого метода запустит метод, определенный в отделенном классе, который, в свою очередь, выведет строку на консоль. Методу Execute передается параметр контекста типа ActivityExecutionContext, о котором вы узнаете больше на протяжении этой главы. Этот метод имеет тип возврата типа ActivityExecutionStatus, который используется исполняющей системой, чтобы определить, завершилось ли действие успешно, все еще продолжается, либо пребывает в одном из других возможных состояний, которые могут описать исполняющей системе рабочего потока, как обстоят дела с данным действием. Возврат из этого метода ActivityExecutionStatus.Closed говорит о том, что действие завершило свою работу и может быть закрыто. Существуют 28 стандартных действий, представленных WF, и в следующем разделе мы приведем примеры некоторых из них вместе со сценариями, где вы можете их использовать. Соглашение по именованию для действий требует добавления суффикса Activity к имени; например, код действия, показанный на рис. 41.2, определен классом CodeActivity. Все стандартные действия определены в пространстве имен System.Workflow. Activities, которое является частью сборки System.Workflow.Activities.dll. Есть две другие сборки, которые составляют WF. Это System.Workflow.ComponentModel.dll и System.Workflow.Runtime.dll. IfElseActivityКак следует из наименования, это действие работает подобно оператору if-else из C#. Когда вы поместите действие IfElseActivity на поверхность конструктора, то увидите примерно то, что показано на рис. 41.3.
IfElseActivity - составное действие, потому что оно создает две ветви (которые сами по себе представляют действие определенного типа; в данном случае - IfElseBranchActivity). Каждая ветвь также является составным действием, унаследованным от SequenceActivity - класса, выполняющего последовательность действий одно за другим, сверху вниз. Как видно на рисунке, конструктор добавляет текст Drop Activities Here (Поместите сюда действия), указывая места, куда должны добавляться дочерние действия. Первая ветвь, показанная на рис. 41.3, содержит восклицательный знак, указывающий на необходимость определения свойства Condition (условие). Условие наследуется от ActivityCondition и используется для определения того, какая ветвь должна выполняться. Когда выполняется IfElseActivity, оно оценивает условие первой ветви, и если оно истинно, тогда эта ветвь выполняется. Если же условие оценивается как ложное, то IfElseActivity проверяет следующую ветвь, и так далее - до тех пор, пока не доберется до последней ветви в действии. Следует отметить, что IfElseActivity может иметь любое количество ветвей, каждая со своим собственным условием. Последней ветви условие не нужно, поскольку она работает как часть else в операторе if-else. Чтобы добавить новую ветвь, вы можете отобразить контекстное меню для действия и выбрать в нем Add Branch (Добавить ветвь) - этот пункт также доступен из меню Workflow (Рабочий поток) внутри Visual Studio. По мере добавления ветвей каждая из них, за исключением последней, будет иметь обязательное условие. В WF определено два стандартных типа условий - CodeCondition и RuleCoditionReference. Класс CodeCondition выполняет метод вашего отделенного класса, который может вернуть true или false. Чтобы создать CodeCondition, отобразите таблицу свойств для IfElseActivity и установите условие Condition в Code Condition, затем введите имя кода, который должен быть исполнен, как показано на рис. 41.4.
Когда вы введете имя метода в таблицу свойств, то конструктор сконструирует метод в вашем отделенном классе, как показано в следующем фрагменте:
Приведенный код устанавливает свойство Result переданного экземпляра ConditionalEventArgs в true, если текущий час находится в диапазоне от 9 утра до 5 вечера. Условия могут быть определены в коде, как показано здесь, но есть и другой вариант - когда условие базируется на правиле (Rule), которое оценивается аналогичным образом. Конструктор Workflow Designer содержит редактор правил, который может быть использован для объявления условий и операторов (во многом подобно оператору if-else, приведенному выше). Эти правила оцениваются во время исполнения на основе текущего состояния рабочего потока. ParallelActivityЭто действие позволяет определить набор действий, выполняемых в параллельной, или, скорее, в псевдопараллельной манере. Когда исполняющая среда рабочего потока планирует действие, она делает это в единственном потоке. Этот поток выполняет первое действие, затем второе и так далее, пока не будут завершены все действия (или пока действие ожидает некоторого ввода). Когда активизируется ParallelActivity, оно проходит по всем ветвям и планирует выполнение каждой из них по очереди. Исполняющая среда рабочего потока поддерживает очередь запланированных к выполнению действий для каждого экземпляра рабочего потока и обычно выполняет их в режиме FIFO (первый пришел, первый обслужен).
Если предположить, что у вас есть ParallelActivity, как показано на рис. 41.5, то это запланирует выполнение sequenceActivity1 и затем sequenceActivity2. Тип SequenceActivity работает, планируя выполнение первого действия вместе с исполняющей средой, и когда оно закончится, планирует второе действие. Этот метод "планирование/ожидание завершения" используется для прогона всех дочерних действий последовательности до тех пор, пока все они не будут выполнены, и тогда все последовательное действие может завершиться. Говоря о том, что SequenceActivity планирует выполнение по одному действию за раз, подразумевается, что очередь, поддерживаемая WorkflowRuntime, постоянно обновляется запланированными действиями. Предположим, что у нас есть параллельное действие P1, которое содержит две последовательности - S1 и S2, каждая из которых состоит из действий кода C1 и С2. Это даст нам очередь запланированных действий, показанную в табл. 41.1.
Здесь очередь сначала обрабатывает первый элемент (параллельное действие P1), которое добавляет два последовательных действия S1 и S2 в очередь рабочего потока. При выполнении действия S1 оно помещает свое первое действие (S1.C1) в конец очереди, и когда это действие будет запланировано и выполнено, помещает в конец очереди свое второе действие. Как видно в приведенном примере, выполнение ParallelActivity происходит не совсем параллельно; на самом деле оно поочередно выполняет компоненты двух последовательных ветвей. Отсюда вы можете сделать вывод, что лучше, когда отдельное действие требует минимального отрезка времени, поскольку с учетом того, что очередь каждого рабочего потока обслуживает только один поток, долго выполняющееся действие может затормозить выполнение прочих действий в очереди. Таким образом, часто возникает ситуация, когда действие может выполняться в течение произвольного отрезка времени, поэтому должен быть способ пометить действие, как "долгоиграющее", чтобы другие действия получили возможность выполняться. Вы можете сделать это, возвратив ActivityExecutionStatus из метода Execute, которое позволяет исполняющей среде узнать о том, что вы обратитесь к ней позднее, когда данное действие завершится. Примером такого действия может служить DelayActivity. CallExternalMethodActivityРабочий поток обычно нуждается в вызове методов извне рабочего потока, и такое действие позволяет определить интерфейс и метод для вызова в этом интерфейсе. WorkflowRuntime поддерживает список служб (помеченных ключом - значением System.Type), которые могут быть доступны через параметр ActivityExecutionContext, переданный методу Execute. Вы можете определить свои собственные службы для добавления в коллекцию и затем обращаться к ним изнутри ваших собственных действий. Например, вы можете построить уровень доступа к данным, представленный как интерфейс службы, и затем подготовить различные реализации этой службы для SQL Server и Oracle. Поскольку действия просто вызывают методы интерфейса, замена SQL Server на Oracle будет совершенно прозрачной для действий, поскольку они просто вызывают методы интерфейса и ничего не знают о конкретной системе управления базами данных. После добавления к рабочему потоку CallExternalMethodActivity вы затем определяете дваобязательных свойства - InterfaceType и MethodName. Тип интерфейса определяет, какая служба времени выполнения будет использована при выполнении действия, а имя метода определяет, какой метод этого интерфейса будет вызван. Когда действие выполняется, оно ищет службу с определенным интерфейсом, запрашивая исполняющий контекст на предмет типа службы, и затем вызывает соответствующий метод этого интерфейса. Вы можете также передать параметры методу внутри рабочего потока - об этом мы поговорим позже, в разделе "Привязка параметров к действиям". DelayActivity Visual StudioБизнес-процессам часто бывает нужно подождать некоторое время перед тем, как завершиться - представьте себе использование рабочего потока для утверждения расходов. Ваш рабочий поток может послать электронное письмо вашему непосредственному начальнику, запрашивая его утверждения ваших расходов. Рабочий поток затем переходит в режим ожидания, в котором он либо ждет утверждения (или отказа), но было бы неплохо определить таймаут, чтобы если ответ не получен, скажем, в течение одного дня, отчет о расходах был бы автоматически перенаправлен более высокопоставленному начальнику в административной иерархии. Действие DelayActivity может формировать часть этого сценария (другую часть реализует ListenActivity, о котором речь пойдет ниже), и его работа заключается в ожидании в течение определенного времени, прежде чем продолжить выполнение рабочего потока. Есть также два пути определения времени ожидания - вы можете либо установить в свойстве TimeoutDuration строку вроде "1.00:00:00" (1 день, ноль часов, ноль минут и ноль секунд), либо предоставить метод, который будет вызван при выполнении действия, который установит продолжительность ожидания в своем коде. Чтобы сделать это, вам понадобится определить значение для свойства InitializeTimeoutDuration действия задержки, что сгенерирует метод в отделенном коде, как показано в следующем фрагменте:
Здесь метод DefineTimeout выполняет приведение sender к типу DelayActivity и затем в коде устанавливает его свойство TimoutDuration в значение TimeSpan. Несмотря на то что в данном примере значение жестко закодировано, более вероятно, что вы будете строить его на основе некоторых других данных - возможно, параметра, переданного в рабочий поток, или значения, прочитанного из конфигурационного файла. Параметры рабочего потока обсуждаются далее в настоящей главе, в разделе "Рабочие потоки". ListenActivityРаспространенная программная конструкция заключается в организации ожидания одного из набора возможных событий; примером может служить метод WaitAny класса System. Threading.WaitHandle. ListenActivity предоставляет возможность сделать это в рабочем потоке, поскольку может определить любое количество ветвей, каждая из которых основана на некотором событии. Действие события - это такое действие, которое реализует интерфейс IEventActivity, определенный в пространстве имен System.Workflow.Activities. В настоящее время есть три таких действия, определенных как стандартные в WF, а именно: DelayActivity, HandleExternalEventActivity и WebServiceInputActivity. Ниже на рис. 41.6 показан рабочий поток, ожидающий либо внешнего ввода, либо истечения некоторого времени - это пример, подходящий для реализации утверждения расходов, о котором мы говорили ранее.
В этом примере действие CallExternalMethodActivity используется в качестве первого действия в рабочем потоке, которое вызывает метод, определенный в интерфейсе службы, который запросит у руководителя утверждения расходов. Поскольку это внешняя служба, запрос может поступить в форме электронной почты, сообщения IM (instance message) либо в любом другом виде, позволяющем известить вашего руководителя о том, что ему нужно обработать запрос на расходы. Затем рабочий поток выполняет действие ListenActivity, которое ожидает ввода от этой внешней службы (утверждения или отказа), и ожидает его в течение заданного периода времени. Когда происходит прослушивание, оно ставит в очередь ожидание как первое действие в каждой ветви, и когда произойдет ожидаемое событие, все прочие ожидания прерываются, и осуществляется обработка остальной части той ветви, где произошло событие. Таким образом, в экземпляре, где отчет о расходах был утвержден, возникает событие Approved и планируется действие PayMe. Если же, однако, ваш руководитель отвергает ваш запрос на расходы, возбуждается событие Rejected, и в данном примере происходит переход к действию Panic (паника). И последнее: если не возникло ни одно из событий - ни Approved, ни Rejected, - тогда по истечении заданного времени ожидания DelayActivity обязательно завершается, и отчет о расходах может быть направлен другому руководителю, возможно, после выполнения его поиска вверх по иерархии в Active Directory. В данном примере пользователь видит диалог, когда выполняется действие RequestApproval, так что в случае, когда осуществляется ожидание, вам также нужно закрыть диалог, то есть выполнить действие по имени HideDialog (см. рис. 41.6). В данном примере использованы некоторые концепции, которые пока еще не были нами раскрыты, например, как идентифицируется экземпляр рабочего потока, и как события попадают обратно в исполняющую среду рабочего потока, и в конечном итоге доставляются правильному экземпляру рабочего потока. Эти концепции будут представлены в разделе "Рабочие потоки". Модель выполнения действийДо сих пор в этой главе мы обсуждали только выполнение действий исполняющей системой через вызов метода Execute. На самом деле действие в процессе своего выполнения может проходить через ряд состояний, что отражено на рис. 41.7.
Сначала действие инициализируется посредством WorkflowRuntime, когда исполняющая система вызывает метод Initialize этого действия. Этому методу передается экземпляр IServiceProvider, который отображает доступные службы внутри исполняющей системы. Эти службы мы обсудим позже, в разделе "Службы рабочих потоков" настоящей главы. Большинство действий ничего не делают в этом методе, но метод все же нужен, чтобы вы могли в нем произвести необходимые начальные установки. Затем исполняющая система вызывает метод Execute, и действие может вернуть любое из значений перечисления ActivityExecutionStatus. Обычно вы будете возвращать из метода значение Closed, которое говорит о том, что действие завершило свою обработку. Однако если вы вернете одно из других значений состояния, то исполняющая система использует его для определения того, в каком состоянии находится действие. Вы можете вернуть из этого метода Executing, сообщив исполняющей системе о том, что у вас еще есть работа, которую нужно доделать - типичный пример этого, это когда у вас есть составное действие, которое должно выполнить свои дочерние элементы. В этом случае ваше действие может запланировать запуск каждого своего дочернего действия и ожидать завершения их работы прежде, чем известить исполняющую систему о том, что данное действие завершилось. |