State Machine


Yet another graph-oriented style of programming allows WF programmers to explicitly describe programs as state machines. In each state, there is a set of possible inputs (stimuli) that will cause the program instance to move from one state to another. What we are exploring in this topic is how to allow the author of a program to explicitly enumerate all possible states for that program instead of using a structured style of programming in which the states are implicit.

The StateMachine activity is shown in Listing B.4. Each node in a state machine graph is just a label (not an activity, as in the case of the Graph activity). As with the Graph activity, transitions describe paths of execution from one state to another, but unlike Graph, a state machine always occupies exactly one state (node) at a timethere is no interleaving. Each transition is also associated with some form of input, whichwhen provided to the state machinecauses the state machine to move from one state to another.

Listing B.4. StateMachine Activity

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Workflow.ComponentModel; using System.Workflow.Runtime; namespace EssentialWF.Activities {   public class Transition : DependencyObject   {     public Transition() : base() { }     public static readonly DependencyProperty       ToProperty = DependencyProperty.Register(         "To",         typeof(string),         typeof(Transition),         new PropertyMetadata(DependencyPropertyOptions.Metadata)       );     public static readonly DependencyProperty       FromProperty = DependencyProperty.Register(         "From",         typeof(string),         typeof(Transition),         new PropertyMetadata(DependencyPropertyOptions.Metadata)       );     public static readonly DependencyProperty       ActivityNameProperty = DependencyProperty.Register(         "ActivityName",         typeof(string),         typeof(Transition),         new PropertyMetadata(DependencyPropertyOptions.Metadata)       );     public static readonly DependencyProperty       InputPropProperty = DependencyProperty.Register(         "InputProp",         typeof(string),         typeof(Transition),         new PropertyMetadata(DependencyPropertyOptions.Metadata)       );     public string To     {       get { return GetValue(ToProperty) as string; }       set { SetValue(ToProperty, value); }     }     public string From     {       get { return GetValue(FromProperty) as string; }       set { SetValue(FromProperty, value); }     }     public string ActivityName     {       get { return GetValue(ActivityNameProperty) as string; }       set { SetValue(ActivityNameProperty, value); }     }     // Specifies the name of an activity property that     // must be set prior to execution of the activity.     // Alternatively, an activity type could be decorated     // to indicate its input property.     public string InputProp     {       get { return GetValue(InputPropProperty) as string; }       set { SetValue(InputPropProperty, value); }     }   }   public class StateMachineQuery { }   public class StateMachineAction   {   public string ActivityName;   public object Input;   }   public class StateMachine : CompositeActivity   {     public StateMachine()       : base()   {     base.SetReadOnlyPropertyValue(StatesProperty,       new List<string>());     base.SetReadOnlyPropertyValue(TransitionsProperty,       new List<Transition>());   }     public static readonly DependencyProperty       StatesProperty = DependencyProperty.Register(         "States",         typeof(List<string>),         typeof(StateMachine),         new PropertyMetadata(DependencyPropertyOptions.Metadata           | DependencyPropertyOptions.ReadOnly,           new Attribute[] {             new DesignerSerializationVisibilityAttribute(               DesignerSerializationVisibility.Content) }         )       );     public static readonly DependencyProperty       TransitionsProperty = DependencyProperty.Register(         "Transitions",         typeof(List<Transition>),         typeof(StateMachine),         new PropertyMetadata(DependencyPropertyOptions.Metadata           | DependencyPropertyOptions.ReadOnly,           new Attribute[] {             new DesignerSerializationVisibilityAttribute(               DesignerSerializationVisibility.Content) }         )       );     [DesignerSerializationVisibility(       DesignerSerializationVisibility.Content)]     public List<string> States     {       get { return GetValue(StatesProperty) as List<string>; }     }     [DesignerSerializationVisibility(       DesignerSerializationVisibility.Content)]     public List<Transition> Transitions     {     get { return GetValue(TransitionsProperty)       as List<Transition>; }     }     private string currentState;     private Transition currentTransition;     private List<string> history;     protected override void Initialize(       IServiceProvider provider)     {       this.currentState = "NONE";       this.currentTransition = null;       this.history = new List<string>();       WorkflowQueuingService qService =         provider.GetService(typeof(WorkflowQueuingService))           as WorkflowQueuingService;       if (!qService.Exists(this.Name))         qService.CreateWorkflowQueue(this.Name, false);       base.Initialize(provider);     }     protected override void Uninitialize(       IServiceProvider provider)     {       this.currentState = "NONE";       this.currentTransition = null;       this.history = null;     WorkflowQueuingService qService =       provider.GetService(typeof(WorkflowQueuingService))         as WorkflowQueuingService;     if (qService.Exists(this.Name))       qService.DeleteWorkflowQueue(this.Name);     base.Uninitialize(provider);   }   protected override ActivityExecutionStatus Execute(     ActivityExecutionContext context)   {     if (EnabledActivities.Count == 0)       return ActivityExecutionStatus.Closed;     this.currentState = "START";     history.Add(currentState);     WorkflowQueuingService qService =       context.GetService<WorkflowQueuingService>();     WorkflowQueue queue = qService.GetWorkflowQueue(this.Name);     queue.QueueItemAvailable += this.ResumeAt;     return ActivityExecutionStatus.Executing;   }   void ResumeAt(object sender, QueueEventArgs e)   {     ActivityExecutionContext context =       sender as ActivityExecutionContext;     WorkflowQueuingService qService =       context.GetService<WorkflowQueuingService>();     WorkflowQueue queue = qService.GetWorkflowQueue(this.Name);     object obj = queue.Dequeue();     StateMachineAction action = obj as StateMachineAction;     if (action != null)     {       if (currentTransition != null)       {         // state transition in progress         Console.WriteLine("action rejected");         return;       }       Transition proposed = null;       foreach (Transition t in this.Transitions)       {         // Find the right transition         if (t.ActivityName.Equals(action.ActivityName)           && t.From.Equals(this.currentState))         {           proposed = t;           break;         }       }       if (proposed == null)       {         Console.WriteLine("action not valid: " + action.ActivityName);         return;       }       foreach (Activity child in EnabledActivities)       {         if (child.Name.Equals(proposed.ActivityName))         {           currentTransition = proposed;           Run(context, child, proposed.InputProp, action.Input);           return;         }       }     }     else // query     {       StateMachineQuery query = obj as StateMachineQuery;       if (query != null)       {         Console.WriteLine("Current State: " + this.currentState);         Console.WriteLine("Current Transition: " + ((currentTransition == null) ? "-none-" : (currentTransition.ActivityName + "<" + currentTransition.From + "," + currentTransition.To + ">")));         Console.Write("History: ");         foreach (string s in this.history)           Console.Write(s + (s.Equals("END") ? "" : "->"));         Console.Write("\n");         if (currentTransition == null)         {           Console.WriteLine("Possible Transitions:");           foreach (Transition t in this.Transitions)           {             if (t.From.Equals(this.currentState))               Console.WriteLine(" " + t.ActivityName + "<" + t.From + "," + t.To  + ">" + " Input=" + t.InputProp);           }         }      }      else      {        Console.WriteLine("unrecognized input: " + obj.ToString());     }   } }   private void Run(ActivityExecutionContext context,     Activity child, string propName, object propValue)   {     ActivityExecutionContextManager manager =       context.ExecutionContextManager;     ActivityExecutionContext c =       manager.CreateExecutionContext(child);     // Use reflection to set the input property value       object o = c.Activity;       o.GetType().GetProperty(propName).GetSetMethod().         Invoke(o, new object[] { propValue });       c.Activity.Closed += ContinueAt;       c.ExecuteActivity(c.Activity);     }     void ContinueAt(object sender,       ActivityExecutionStatusChangedEventArgs e)    {     e.Activity.Closed -= this.ContinueAt;     ActivityExecutionContext context =       sender as ActivityExecutionContext;     ActivityExecutionContextManager manager =       context.ExecutionContextManager;     ActivityExecutionContext c =       manager.GetExecutionContext(e.Activity);     manager.CompleteExecutionContext(c, false);     currentState = this.currentTransition.To;     history.Add(currentState);     currentTransition = null;     if (currentState.Equals("END"))       context.CloseActivity();     }     // Cancellation logic     ...   } } 


In a state machine there are a few different places where child activities can be used: A specific activity may need to be executed whenever a particular state is entered; a specific activity may need to be executed whenever a particular state is exited; a specific activity may need to be executed whenever a particular transition is traversed. In the StateMachine activity shown in Listing B.4, we only associate activities with transitions, and not with entry into or exit from states, but this could easily be added.

The StateMachine activity creates a WF program queue, on which it receives inputs of two kinds. A StateMachineAction is used to effect a transition from one state to another; data is provided to the activity associated with a transition using a simple reflection-based design in which one property of the activity is designated as its input. A StateMachineQuery is used to obtain information about the current state of the state machine, the history of states traversed, and the possible next steps (allowed inputs).

Here is an example StateMachine program:

 <StateMachine x:Name="m1" States="a, b, c, d" xmlns="http://EssentialWF/Activities xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">   <StateMachine.Transitions>     <Transition From="START" To="a" InputProp="Text" ActivityName="w1" />     <Transition From="START" To="c" InputProp="Text" ActivityName="w2" />     <Transition From="a" To="a" InputProp="Text" ActivityName="w3" />     <Transition From="a" To="b" InputProp="Text" ActivityName="w4" />     <Transition From="b" To="c" InputProp="Text" ActivityName="w5" />     <Transition From="c" To="b" InputProp="Text" ActivityName="w6" />     <Transition From="c" To="d" InputProp="Text" ActivityName="w7" />     <Transition From="d" To="START" InputProp="Text" ActivityName="w8" />     <Transition From="d" To="END" InputProp="Text" ActivityName="w9" />   </StateMachine.Transitions>   <WriteLine Text="1" x:Name="w1" />   <WriteLine Text="2" x:Name="w2" />   <WriteLine Text="3" x:Name="w3" />   <WriteLine Text="4" x:Name="w4" />   <WriteLine Text="5" x:Name="w5" />   <WriteLine Text="6" x:Name="w6" />   <WriteLine Text="7" x:Name="w7" />   <WriteLine Text="8" x:Name="w8" />   <WriteLine Text="9" x:Name="w9" /> </StateMachine> 


The preceding program can be visualized as shown in Figure B.3.

Figure B.3. A State Machine-based WF program


The output of the program will depend upon the series of StateMachineAction objects that are enqueued into the state machine's WF program queue. For example, we could traverse the states START, c, d, END. We could also traverse the states START, a, a, b, c, d, END. There are an unbounded number of outcomes for this state machine since there are loops in the possible execution paths.

In order to remain true to the state machine concept, in which the program is always in one state, the child activities of StateMachine should not create bookmarks. In this way, when input arrives, the StateMachine selects the appropriate transition (if any) and executes the child activity associated with that transition. The StateMachine can be said to traverse a transition (more or less) immediately if a single stimulus is always enough to move the state machine from one state to another. This, however, is not enforced in the preceding state machine implementation; it is possible to create transitions that do create bookmarks. For this reason, whenever a transition is in progress, any inputs of type StateMachineAction that are sent to the state machine are rejected.

For many if not most solutions, it makes sense to design programs not as explicit state machines but as structured programs that exploit (in their descriptions) the interleaving and control flow patterns that occur in the real-world process being modeled. Enumeration of all possible states is likely to be cumbersome. The StateMachine activity forces the WF program author to break down the logic of the program's control flow into uninterruptible segments. Generally speaking, this design approach does not scale well due to the explosion in the state space of even moderately sized programs, but can be useful in cases where the real-world process that is being modeled is naturally described in terms of a set of logical states. Consider the following structured program:

 <Interleave>   <Sequence>     <Read x:Name="r1" />     <Read x:Name="r2" />   </Sequence>   <Sequence>     <Read x:Name="r3" />     <Read x:Name="r4" />   </Sequence>   <Sequence>     <Read x:Name="r5" />     <Read x:Name="r6" />     <Read x:Name="r7" />     <Read x:Name="r8" />   </Sequence> </Interleave> 


When an instance of the program above begins executing, it quickly becomes idle waiting for input directed at either r1, r3, or r5. This is one state (let's call it "135") for the program. If r3 completes its execution, the program moves to another state in which it can receive input for either r1, r4, or r5. This is state "145". But it also could have moved to state "235" or "136" if the bookmark for either r1 or r5 had been resumed instead. There are many possible paths of execution for this program (and a much larger number of paths for more complex programs that use interleaving), with a correspondingly large number of states needed to explicitly describe all possible paths.

The structured style of programming allows this logic to be expressed compactly. Of course, it is also possible to specify the logic of the preceding program using our Graph activity (or, for that matter, a PrioritizedInterleave). It is up to you to decide what style of programming suits the solution you are building, and whether custom composite activities (beyond even the ones shown in this appendix) are warranted.




Essential Windows Workflow Foundation
Essential Windows Workflow Foundation
ISBN: 0321399838
EAN: 2147483647
Year: 2006
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net