Обзор рабочего потока Windows Workflow. Часть 3

Источник: rsdn

Читать часть 2

Прикрепленные свойства

При регистрации свойств зависимости вы можете вызвать метод RegisterAttached для создания прикрепленного свойства. Прикрепленное свойство - это такое свойство, которое определено в одном классе, но отображается в другом. В данном случае вы определяете свойство в DaysOfWeekActivity, но на самом деле это свойство отображается в пользовательском интерфейсе как прикрепленное к последовательному действию.

public static DependencyProperty
   WeekdayProperty =
      DependencyProperty.RegisterAttached("Weekday",
         typeof(WeekdayEnum), typeof(DaysOfWeekActivity),
      new PropertyMetadata(DependencyPropertyOptions.Metadata));

Последняя строка позволяет специфицировать дополнительную информацию о свойстве; в данном примере специфицировано, что этим свойством будет Metadata.

Свойства Metadata отличаются от обычных свойств в том отношении, что они эффективно читаются только во время выполнения. Вы можете воспринимать свойство Metadata как нечто подобное объявлению констант внутри C#. Вы не можете изменить константы во время выполнения программы, и точно также вы не можете изменять свойства Metadata во время выполнения рабочего потока.

В данном примере вы хотите определить дни активизации действия, поэтому вы можете установить в Designer это поле в "Saturday, Sunday". В коде, выданном для рабочего потока, вы можете увидеть объявления, подобные показанным ниже.

this.sequenceActivity1.SetValue
   (DaysOfWeekActivity.WeekdayProperty,
      ((WeekdayEnum)((WeekdayEnum.Sunday / WeekdayEnum.Saturday))));

В дополнение к определению свойства зависимости вам понадобятся методы для получения и установки этого значения для произвольного действия. Это обычно определяется как статический метод составного действия и показано в следующем коде:

public static void SetWeekday( Activity activity, object value )
{
   if ( null == activity )
      throw new ArgumentNullException( "activity" );

   if ( null == value )
      throw new ArgumentNullException( "value" );

   activity.SetValue( DaysOfWeekActivity.WeekdayProperty, value );
}

public static object GetWeekday( Activity activity )
{
   if ( null == activity )
      throw new ArgumentNullException( "activity" );

   return activity.GetValue( DaysOfWeekActivity.WeekdayProperty );
}

Есть еще два изменения, которые вы должны внести, чтобы дополнительное свойство могло отображаться как присоединенное к SequenceActivity. Первое - это создать поставщика расширителей, который сообщит Visual Studio о необходимости включения дополнительного свойства в последовательное действие, и второе - зарегистрировать этого поставщика, что делается переопределением метода Initialize в Activity Designer и добавлением следующего кода к нему:

protected override void Initialize( Activity activity )
{
   base.Initialize( activity );
   IExtenderListService iels = base.GetService( typeof( IExtenderListService ) )
                               as IExtenderListService;

   if ( null != iels )
   {
      bool extenderExists = false;
      foreach ( IExtenderProvider provider in iels.GetExtenderProviders() )
      {
         if ( provider.GetType() == typeof( WeekdayExtenderProvider ) )
         {
            extenderExists = true;
            break;
         }
      }

      if ( !extenderExists )
      {
         IExtenderProviderService ieps =
            base.GetService( typeof( IExtenderProviderService ) )
            as IExtenderProviderService;

         if ( null != ieps )
            ieps.AddExtenderProvider( new WeekdayExtenderProvider() );
      }
   }
}

Вызовы GetService в приведенном коде предназначены для того, чтобы позволить пользовательскому конструктору запросить службы, предоставляемыехостом (в данном случае им является Visual Studio ). Вы запрашиваете у Visual Studio IextenderListService, который предоставляет способ перечислить всех доступных поставщиков расширителей, и если не будет найдено ни одного экземпляра WeekdayExtenderProvider, тогда запрашивается IExtenderProviderService, и добавляется новый поставщик. Ниже показан код поставщика расширителей.

[ProvideProperty("Weekday", typeof(SequenceActivity))]
public class WeekdayExtenderProvider : IExtenderProvider
{
   bool IExtenderProvider.CanExtend(object extendee)
   {
      bool canExtend = false;
      if ((this != extendee) && (extendee is SequenceActivity))
      {
         Activity parent = ((Activity)extendee).Parent;
         if (null != parent)
            canExtend = parent is DaysOfWeekActivity;
      }
      return canExtend;
   }
   public WeekdayEnum GetWeekday(Activity activity)
   {
      WeekdayEnum weekday = WeekdayEnum.None;
      Activity parent = activity.Parent;
      if ((null != parent) && (parent is DaysOfWeekActivity))
         weekday = (WeekdayEnum)DaysOfWeekActivity.GetWeekday(activity);
      return weekday;
   }
   public void SetWeekday(Activity activity, WeekdayEnum weekday)
   {
      Activity parent = activity.Parent;
      if ((null != parent) && (parent is DaysOfWeekActivity))
         DaysOfWeekActivity.SetWeekday(activity, weekday);
   }
}

Поставщик расширителей снабжен свойствами, которые он предоставляет, и для каждого из этих свойств он должен обеспечить общедоступные методы Get<Property> и Set<Property>. Имена этих методов должны соответствовать именам свойства с соответствующим префиксом Get или Set.

После внесения показанных выше изменений в Designer и добавления поставщика расширений, когда вы щелкнете на последовательном действии в визуальном конструкторе, то в Visual Studio увидите свойства, показанные на рис. 41.14.


Рис. 41.14. Прикрепленные свойства

Поставщики расширителей используются в .NET и для других целей. Одним из распространенных применений является добавление всплывающих подсказок (tooltips) к элементам управления в проекте Windows Forms. Когда вы добавляете в форму элемент-подсказку, это регистрирует расширитель и добавляет свойство Tooltip к каждому элементу управления в форме.

Рабочие потоки

До этого момента в настоящей главе внимание концентрировалось на действиях, но не говорилось о рабочих потоках. Рабочий поток - это просто список действий, и на самом деле рабочий поток сам по себе является еще одним типом действий. Применение такой модели упрощает механизм исполняющей системы, поскольку ему достаточно знать, как следует выполнять только один тип объектов - любой, унаследованный от класса Activity.

Каждый экземпляр рабочего потока уникально идентифицирован свойством InstanceId, то есть Guid, который может быть назначен исполняющей системой, или же этот Guid может быть представлен исполняющей системе вашим кодом - обычно это делается для корреляции работающего экземпляра рабочего потока с некоторым внешним источником данных, находящимся вне рабочего потока, таким как строка базы данных. Вы можете обратиться к определенному экземпляру рабочего потока посредством метода GetWorkflow(Guid) класса WorkflowRuntime.

Существуют два типа рабочих потоков, доступных в WF - последовательный и конечный автомат.

Последовательные рабочие потоки

Корневое действие последовательного рабочего потока - это SequentialWorkflowActivity. Этот класс наследован от SequenceActivity, который вы уже видели, и он определяет два события, к которым при необходимости вы можете присоединить обработчики. Эти события - Initialized и Completed.

Последовательный рабочий поток начинает работу, выполняя находящееся в нем первое дочернее действие, и обычно продолжается до тех пор, пока не выполнит все остальные дочерние действия. Есть несколько ситуаций, когда рабочий поток не выполняет всех своих действий. Одна - это когда возникает исключение в процессе выполнения рабочего потока, а другая - когда внутри потока встречается TerminateActivity.

Рабочий поток может и не выполняться постоянно; например, когда встречается DelayActivity, то поток входит в состояние ожидания, и тогда он может быть удален из памяти, если определена служба постоянства потоков. Постоянство потоков описано далее в разделе "Служба постоянства".

Рабочие потоки типа конечных автоматов

Поток типа конечного автомата используется в случае, когда у вас есть процесс, который может пребывать в одном из нескольких состояний, и переходы из одного состояния в другое могут выполняться посредством передачи данных в рабочий поток.

Примером может служить рабочий поток, используемый для управления зданием. В этом случае вы можете моделировать класс двери, которая может быть закрыта или открыта, и класс замка, который может быть замкнут или незамкнут. Изначально, когда вы загружаете систему (или здание!), вы начинаете с известного состояния; для простоты предположим, что все двери закрыты и заперты на замок, так что состояние данной двери - "закрыта и заперта".

Когда сотрудник вводит свой код доступа на внешней двери, он тем самым посылает событие рабочему потоку, включающее такие подробности, как введенный код и, возможно, идентификатор пользователя. Затем вам может понадобиться обратиться к базе данных для извлечения подробностей, таких как, например, имеет ли данное лицо право открывать выбранную дверь в это время дня, и если имеет, то рабочий поток должен изменить состояние двери с начального на состояние "открыта и не заперта".

Из этого состояния есть два потенциальных пути - сотрудник открывает дверь (вы знаете это, поскольку дверь снабжена сенсором открытия/закрытия), или же он решит не входить, поскольку забыл что-то у себя в машине, и тогда после небольшой паузы вы запираете дверь. Таким образом, дверь может перейти в свое состояние "закрыта и заперта" или же в состояние "открыта и не заперта".

Теперь предположим, что сотрудник вошел в здание и закрыл за собой дверь; опять же вы должны выполнить переход из состояния "открыта и не заперта" в состояние "закрыта и не заперта", и после паузы должен произойти переход в состояние "закрыта и заперта". Вы также можете выдать сигнал тревоги, если дверь осталась "открытой и не запертой" в течение длительного периода времени.

Смоделировать эту ситуацию в среде Windows Workflow необыкновенно просто. Вам нужно определить состояния, которые может принимать система, и затем определить события, которые могут переводить ее из одного состояния в другое. В табл. 41.2 описаны состояния системы и подробности переходов, которые возможны из каждого состояния, а также ввод (как внешний, так и внутренний), изменяющий эти состояния.

Одно из других средств, которые вы можете решить добавить в систему - это способность реагировать на сигнал пожарной тревоги. Когда сигнал тревоги будет выключен, вам нужно отпереть все двери, чтобы все могли покинуть здание, а пожарная команда - проникнуть в него. Вы можете смоделировать это как финальное состояние рабочего потока управляющего дверью, поскольку из этого состояния вся система должна быть сброшена, как только пожарная тревога будет отменена.

Состояние Переход
Закрыта и заперта Это начальное состояние системы.
В ответ на вставку пользователем карточки (и успешной проверки права доступа) состояние изменяется в "закрыта и не заперта", то есть замок двери отпирается электроникой
Закрыта и не заперта Когда дверь находится в этом состоянии, можетпроизойти одно из двух событий:
(1) пользователь открывает дверь - выполняется переход в состояние "открыта и не заперта";
(2) истекает время таймера и дверь возвращается в состояние "закрыта и заперта"
Открыта и не заперта Из этого состояния рабочий поток может перейти только в состояние "закрыта и не заперта"
Пожарная тревога Это состояние - финальное для рабочего потока, и в него можно перейти из любого другого состояния
Таблица 41.2. Состояния и переходы системы

Рабочий поток на рис. 41.15 определяет конечный автомат и показывает состояния, которые он может принимать, а линии означают возможные переходы из одного состояния в другое.


Рис. 41.15. Определение конечного автомата

Начальное состояние рабочего потока смоделировано действием ClosedLocked. Оно состоит из некоторого кода инициализации (запирающего дверь) и затем действия на основе события, которое ожидает внешнего события - в данном случае, ввода сотрудником собственного кода доступа в здание. Каждое из показанных действий внутри фигуры состояния состоит из последовательных рабочих потоков - таким образом, был определен рабочий поток инициализации системы (CLInitialize) и рабочий поток, отвечающий на внешние события, возникающие при воде сотрудником его PIN-кода (RequestEntry). Взгляните на определение рабочего потока RequestEntry, как он изображен на рис. 41.16.


Рис. 41.16. Определение рабочего потока RequestEntry

Каждое состояние состоит из ряда подпотоков, каждый из которых имеет управляемое событием действие в начале, а затем некоторое количество других действий, которые формируют обрабатывающий код внутри состояния. На рис. 41.16 имеется действие HandleExternalEventActivity в начале, которое ожидает ввода PIN. Затем выполняется его проверка, и если код действителен, рабочий поток переходит в состояние ClosedUnlocked.

Состояние ClosedUnlocked состоит из двух рабочих потоков - один реагирует на событие открытия двери, которое переводит рабочий поток в состояние OpenUnlocked, а другой, который содержит действие задержки, используется для перехода в состояние ClosedLocked. Действие, управляемое состоянием, работает в манере, аналогичной действию ListenActivity, показанному ранее в этой главе - это состояние состоит из ряда управляемых событиями рабочих потоков, и любое возникающее событие обрабатывается только одним рабочим потоком.

Для поддержки рабочего потока вы должны быть готовы инициировать события в системе, чтобы вызвать изменения состояния системы. Это делается за счет использования интерфейса и реализации этого интерфейса, и такая пара объектов называется внешней службой (external service). Ниже в этой главе будет описан интерфейс, используемый в данном конечном автомате.

Передача параметров рабочему потоку

Типичный рабочий поток для своего выполнения Visual Studio требует некоторых данных. Это может быть идентификатор заказа для потока обработки заказов, идентификатор учетной записи покупателя для рабочего потока обработки платежей либо любые другие необходимые данные.

Механизм передачи параметров для рабочих потоков несколько отличается от того же механизма в стандартных классах .NET, где обычно вы передаете параметры при вызове метода. Для рабочего потока вы передаете параметры, сохраняя их в словаре в виде пар "имя-значение", и когда вы конструируете рабочий поток, то проходите по этому словарю. Когда WF планирует на выполнение рабочий поток, эти пары "имя-значение" используются для установки значений общедоступных свойств экземпляра рабочего потока. Каждое имя параметра проверяется по общедоступным свойствам рабочего потока и, если обнаружено соответствие, вызывается средство доступа set этого свойства и ему передается значение параметра. Если вы добавляете в словарь пару "имязначение", причем имя не соответствует свойству рабочего потока, то при попытке сконструировать такой рабочий поток будет возбуждено исключение.

В качестве примера рассмотрим следующий рабочий поток, определяющий целочисленное свойство OrderID:

public class OrderProcessingWorkflow: SequentialWorkflowActivity
{
   public int OrderID
   {
      get { return _orderID; }
      set { _orderID = value; }
   }
   private int _orderID;
}

Следующий фрагмент показывает, как можно передать параметр - идентификатор заказа экземпляру этого рабочего потока:

WorkflowRuntime runtime = new WorkflowRuntime ();
Dictionary<string,object> parms = new Dictionary<string,object>();
parms.Add("OrderID", 12345) ;
WorkflowInstance instance =
   runtime.CreateWorkflow(typeof(OrderProcessingWorkflow), parms);
instance.Start();
... //Прочий код

В приведенном примере конструируется словарь Dictionary<string, object>, содержащий параметры, которые вы хотите передать рабочему потоку, и затем использовать при его конструировании. Приведенный выше код включает классы WorkflowRuntime и WorkflowInstance, которые еще не были описаны;о них речь пойдет в разделе "Хостинг рабочих потоков" далее в главе.

Читать часть 4


Страница сайта http://185.71.96.61
Оригинал находится по адресу http://185.71.96.61/home.asp?artId=23755