Graph


As an example of another general-purpose composite activity, consider an activity called Graph (shown in Listing B.2) that represents a graph. The nodes of the graph are the Graph activity's child activities. The arcs of the graph (connections between nodes) are specified as metadata that is held by Graph. For simplicity's sake, we will limit ourselves here to acyclic graphs but there is no fundamental reason to disallow more complex graphs with cycles.

Listing B.2. Graph Activity

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class Graph : CompositeActivity   {     // All transitions must be true for a join     // to take place     private Dictionary<string, bool> transitionStatus;     // If true, we have hit an exit activity and are     // in the process of cancelling other activities     private bool exiting;     public Graph() : base()     {       base.SetReadOnlyPropertyValue(Graph.ArcsProperty,         new List<Arc>());       }       public static readonly DependencyProperty         ArcsProperty = DependencyProperty.Register(           "Arcs",           typeof(List<Arc>),           typeof(Graph),           new PropertyMetadata(DependencyPropertyOptions.Metadata |             DependencyPropertyOptions.ReadOnly, new Attribute[]               { new DesignerSerializationVisibilityAttribute(                 DesignerSerializationVisibility.Content) }           )         );       // Attached to exactly one child activity       public static readonly DependencyProperty IsEntryProperty =         DependencyProperty.RegisterAttached(           "IsEntry",           typeof(bool),           typeof(Graph),           new PropertyMetadata(DependencyPropertyOptions.Metadata)         );       public static object GetIsEntry(object dependencyObject)       {         DependencyObject o = dependencyObject as DependencyObject;         return o.GetValue(Graph.IsEntryProperty);       }       public static void SetIsEntry(object dependencyObject,         object value)       {         DependencyObject o = dependencyObject as DependencyObject;         o.SetValue(Graph.IsEntryProperty, value);       }       // Attached to zero or more child activities       public static readonly DependencyProperty IsExitProperty =         DependencyProperty.RegisterAttached(           "IsExit",           typeof(bool),           typeof(Graph),           new PropertyMetadata(DependencyPropertyOptions.Metadata)         );       public static object GetIsExit(object dependencyObject)       {         DependencyObject o = dependencyObject as DependencyObject;         return o.GetValue(Graph.IsExitProperty);       }       public static void SetIsExit(object dependencyObject,         object value)       {         DependencyObject o = dependencyObject as DependencyObject;         o.SetValue(Graph.IsExitProperty, value);       }       [DesignerSerializationVisibility(         DesignerSerializationVisibility.Content)]       public List<Arc> Arcs       {         get { return GetValue(ArcsProperty) as List<Arc>; }       }       protected override void Initialize(         IServiceProvider provider)       {         exiting = false;         transitionStatus = new Dictionary<string, bool>();         foreach (Arc arc in this.Arcs)         {           transitionStatus.Add(arc.name, false);         }         base.Initialize(provider);       }       protected override void Uninitialize(IServiceProvider provider)       {         transitionStatus = null;         base.Uninitialize(provider);       }       protected override ActivityExecutionStatus Execute(         ActivityExecutionContext context)       {         if (EnabledActivities.Count == 0)           return ActivityExecutionStatus.Closed;         foreach (Activity child in EnabledActivities)         {           // Graph validation logic ensures one entry activity           bool entry = (bool)Graph.GetIsEntry(child);           if (entry)           {             Run(context, child);             break;         }       }         return ActivityExecutionStatus.Executing;       }       private void Run(ActivityExecutionContext context,         Activity child)       {         // we don't necessarily need to dynamically         // create an AEC, but let's do it anyway         // so we can more easily add looping later         ActivityExecutionContextManager manager =           context.ExecutionContextManager;         ActivityExecutionContext c =           manager.CreateExecutionContext(child);         c.Activity.Closed += this.ContinueAt;         c.ExecuteActivity(c.Activity);       }       void ContinueAt(object sender,         ActivityExecutionStatusChangedEventArgs e)       {         e.Activity.Closed -= this.ContinueAt;         ActivityExecutionContext context =           sender as ActivityExecutionContext;         // get the name before completing the AEC         string completedChildName = e.Activity.Name;         bool exitNow = (bool)Graph.GetIsExit(e.Activity);         ActivityExecutionContextManager manager =           context.ExecutionContextManager;         ActivityExecutionContext c =           manager.GetExecutionContext(e.Activity);         manager.CompleteExecutionContext(c, false);       if (exiting || exitNow)       {           // no executing child activities           if (manager.ExecutionContexts.Count == 0)             context.CloseActivity();         else if (exitNow) // just completed an exit activity         {           exiting = true;           foreach (ActivityExecutionContext ctx             in manager.ExecutionContexts)           {             if (ctx.Activity.ExecutionStatus ==               ActivityExecutionStatus.Executing)             {               ctx.CancelActivity(ctx.Activity);             }           }         }       }     else     {       // mark all outgoing transitions as true       foreach (Arc arc in this.Arcs)       {         if (arc.FromActivity.Equals(completedChildName))           this.transitionStatus[arc.name] = true;         }         foreach (Activity child in EnabledActivities)         {           bool entry = (bool)Graph.GetIsEntry(child);         if (!entry)         {           // a child activity can run only           // if all incoming transitions are true           // and it is not the entry activity           bool canrun = true;           foreach (Arc arc in this.Arcs)           {             if (arc.ToActivity.Equals(child.Name))               if (transitionStatus[arc.name] == false)                 canrun = false;           }           if (canrun)           {             // when we run a child activity,             // mark its incoming transitions as false             foreach (Arc arc in this.Arcs)             {               if (arc.ToActivity.Equals(child.Name))                 transitionStatus[arc.name] = false;               }               Run(context, child);             }           }         }       }     }     // Cancellation logic     ...   } } 


One child activity of the Graph must be marked as the "entry" activitythe activity whose execution is the logical starting point for the set of child activities. Zero or more activities are marked as "exit" activities. If an "exit" activity completes successfully, all pending (still executing) activities elsewhere in the graph are canceled and the graph itself can report its own completion.

We can use the Graph activity to write WF programs that look like this (in XAML):

 <Graph x:Name="graph1" xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">   <Graph.Arcs>     <Arc FromActivity="A" ToActivity="B" />     <Arc FromActivity="B" ToActivity="C" />     <Arc FromActivity="B" ToActivity="D" />     <Arc FromActivity="C" ToActivity="E" />     <Arc FromActivity="D" ToActivity="E" />     <Arc FromActivity="E" ToActivity="F" />   </Graph.Arcs>   <WriteLine x:Name="C" Text="c" />   <WriteLine x:Name="A" Text="a" Graph.IsEntry="True" />   <WriteLine x:Name="E" Text="e" />   <WriteLine x:Name="D" Text="d" />   <WriteLine x:Name="F" Text="f" Graph.IsExit="True" />   <WriteLine x:Name="B" Text="b" /> </Graph> 


The preceding WF program is a composition of activities that can be visualized as shown in Figure B.1.

Figure B.1. A graph-based WF program


Each node in the graph is an activity. The activities in the example are all of type WriteLine but in fact there is no restriction at all on the type of child activity that is allowed within a Graphthese activities can be Sequence, WriteLine, Interleave, Read, or any activity that you choose, even another Graph.

The order in which the child activities of Graph execute is determined by the arcs that are captured as metadata associated with the Graph. In the preceding example, activity B acts like a split because the path of execution diverges at precisely that point in the program (activities C and D execute subsequently in an interleaved manner). Activity E acts like a join because two paths of execution are merged at precisely that point in the program (activity E does not execute until both C and D are complete).

The metadata of the Graph activity is just a set of arcs. The associated Arc type is a straightforward class with two dependency properties that hold the names of the activities connected by an arc:

 using System; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class Arc : DependencyObject   {     internal string name;     public Arc() : base()     {       name = Guid.NewGuid().ToString();     }     public Arc(string from, string to)       : this()     {       this.FromActivity = from;       this.ToActivity = to;     }     public static readonly DependencyProperty       FromActivityProperty = DependencyProperty.Register(         "FromActivity",         typeof(string),         typeof(Arc),         new PropertyMetadata(DependencyPropertyOptions.Metadata)       );     public static readonly DependencyProperty       ToActivityProperty = DependencyProperty.Register(         "ToActivity",         typeof(string),         typeof(Arc),         new PropertyMetadata(DependencyPropertyOptions.Metadata)       );     public string FromActivity     {       get { return GetValue(FromActivityProperty) as string; }       set { SetValue(FromActivityProperty, value); }     }     public string ToActivity     {       get { return GetValue(ToActivityProperty) as string; }       set { SetValue(ToActivityProperty, value); }     }   } } 


The code for the preceding Graph activity is not quite complete; it requires validation logic to ensure that the IsEntry and IsExit properties are used appropriately. It also requires standard cancellation logic, as discussed in Chapter 4.

The Graph activity can be enhanced to allow for the association of conditions with the split and join points in the graph, and also to allow loops.




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