WF Program Instance Lifecycle


We have thus far looked at the various aspects of WF program instance management in a somewhat piecemeal fashion. If we look at instance management holistically, we find that the lifecycle of any WF program instance is usefully described by a state diagram, shown in Figure 5.8.

Figure 5.8. WF program instance lifecycle


Creating a new WF program instance leaves the instance in the Created state. Starting the instance moves it to the Running state. When it completes its execution, the instance is in a Completed state. A running instance can be suspended; a suspended instance can be resumed. An instance that is not completed can be terminated, which moves it to the Terminated state.

The states in Figure 5.8 correspond to the values of the WorkflowStatus enumeration, shown in Listing 5.21, and defined in the System.Workflow.Runtime namespace.

Listing 5.21. WorkflowStatus

 namespace System.Workflow.Runtime {   public enum WorkflowStatus   {     Created,     Running,     Completed,     Suspended,     Terminated   } } 


The values of the WorkflowStatus enum correspond to the values you will find in the Status column of the InstanceState table used by the SqlWorkflowPersistenceService. These values are summarized in Table 5.2.

Table 5.2. WorkflowStatus Values

WorkflowStatus

Value

Created

4

Running

0

Completed

1

Suspended

2

Terminated

3


One good way to get a feel for the control operations that allow a host to manage instances of WF programs it to write a simple application that lets you initiate these operations as commands at the console. Such an application is not hard to write, and is shown in Listing 5.22.

Listing 5.22. Application that Manages WF Program Instances

 using System; using System.Collections.ObjectModel; using System.IO; using System.Xml; using System.Workflow.ComponentModel.Compiler; using System.Workflow.Runtime; namespace EssentialWF.Host.Chapter5 {   class Program   {     // Nice and small WF program!     // See Listing 5.24 for the Echo activity type     static readonly string ECHO =       "<Echo xmlns=\"http://EssentialWF/Activities\" />";     static string ConnectionString =       "Initial Catalog=SqlPersistenceService;Data Source=localhost;Integrated Security=SSPI;";     static void Main()     {       using (WorkflowRuntime runtime = new WorkflowRuntime())       {       SqlWorkflowPersistenceService persistenceService =         new SqlWorkflowPersistenceService(ConnectionString);       runtime.AddService(persistenceService);       TypeProvider typeProvider = new TypeProvider(null);       typeProvider.AddAssemblyReference("EssentialWF.dll");       runtime.AddService(typeProvider);       runtime.StartRuntime();       runtime.WorkflowAborted += WorkflowAborted;       runtime.WorkflowCompleted += WorkflowCompleted;       runtime.WorkflowCreated += WorkflowCreated;       runtime.WorkflowIdled += WorkflowIdled;       runtime.WorkflowLoaded += WorkflowLoaded;       runtime.WorkflowPersisted += WorkflowPersisted;       runtime.WorkflowResumed += WorkflowResumed;       runtime.WorkflowStarted += WorkflowStarted;       runtime.WorkflowSuspended += WorkflowSuspended;       runtime.WorkflowTerminated += WorkflowTerminated;       runtime.WorkflowUnloaded += WorkflowUnloaded;       runtime.ServicesExceptionNotHandled +=         ServicesExceptionNotHandled;       WorkflowInstance instance = null;       string bookmarkName = null;       while (true)       {         string s = Console.ReadLine();         if (s.Equals("quit")) break;         try         {           if (s.Equals("create"))           {             using(XmlReader reader = XmlReader.Create(new               StringReader(ECHO)))             {               instance = runtime.CreateWorkflow(reader);             }             bookmarkName = instance.GetWorkflowDefinition().Name;           }           else if (s.StartsWith("get"))           {             string[] tokens = s.Split(new char[] { ' ' });             Guid handle = new Guid(tokens[1]); // InstanceId             instance = runtime.GetWorkflow(handle);               bookmarkName = instance.GetWorkflowDefinition().Name;             }             else if (s.Equals("abort")) instance.Abort();             else if (s.Equals("load")) instance.Load();             else if (s.Equals("resume")) instance.Resume();             else if (s.Equals("start")) instance.Start();             else if (s.Equals("suspend"))               instance.Suspend("user says to suspend");             else if (s.Equals("terminate"))               instance.Terminate("user says to terminate");             else if (s.Equals("tryunload")) instance.TryUnload();             else if (s.Equals("unload")) instance.Unload();             else instance.EnqueueItem(bookmarkName, s, null, null);           }           catch (Exception e)           {             Console.WriteLine(e.GetType().Name + ": " + e.Message);           }         }         runtime.StopRuntime();       }     }     /* *** see Listing 5.19 for event handlers *** */   } } 


Our application subscribes to all of the events available on the WorkflowRuntime, so that we can see exactly when these events occur. The event handlers are shown in Listing 5.23.

Listing 5.23. Event Handlers for Application of Listing 5.22

 static void WorkflowUnloaded(object sender, WorkflowEventArgs e) {   Console.WriteLine("WorkflowUnloaded"); } static void WorkflowTerminated(object sender,   WorkflowTerminatedEventArgs e) {   Console.WriteLine("WorkflowTerminated with " +     e.Exception.GetType().Name + ": " + e.Exception.Message); } static void WorkflowSuspended(object sender,   WorkflowSuspendedEventArgs e) {   Console.WriteLine("WorkflowSuspended: " + e.Error); } static void WorkflowStarted(object sender, WorkflowEventArgs e) {   Console.WriteLine("WorkflowStarted"); } static void WorkflowResumed(object sender, WorkflowEventArgs e) {   Console.WriteLine("WorkflowResumed"); } static void WorkflowPersisted(object sender, WorkflowEventArgs e) {   Console.WriteLine("WorkflowPersisted"); } static void WorkflowLoaded(object sender, WorkflowEventArgs e) {   Console.WriteLine("WorkflowLoaded"); } static void WorkflowIdled(object sender, WorkflowEventArgs e) {   Console.WriteLine("WorkflowIdled"); } static void WorkflowCreated(object sender, WorkflowEventArgs e) {   Console.WriteLine("WorkflowCreated " +     e.WorkflowInstance.InstanceId); } static void WorkflowAborted(object sender, WorkflowEventArgs e) {   Console.WriteLine("WorkflowAborted"); } static void ServicesExceptionNotHandled(object sender,   ServicesExceptionNotHandledEventArgs e) {   Console.WriteLine("ServicesExceptionNotHandled: " +     e.Exception.GetType().Name); } static void WorkflowCompleted(object sender,   WorkflowCompletedEventArgs e) {   Console.Write("WorkflowCompleted");   foreach (string key in e.OutputParameters.Keys)   {     object o = e.OutputParameters[key];     Console.Write(" " + key + "=" + o.ToString());   }   Console.Write("\n"); } 


The application in Listing 5.22 uses a simple activity called Echo, and creates WF programs that are simply instances of this activity. The Echo activity is shown in Listing 5.24. Running the application will let you confirm the behavior of each of the control operations firsthand. Furthermore, you can get a feel for how persistence works by starting the application and starting some instances, and then quitting the application. At this point you can verify that the instances have been saved in your SQL Server database. Restarting the application will allow you to continue the execution of the instances that had previously been persisted.

Listing 5.24. The Echo Activity

 using System; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Serialization; using System.Workflow.Runtime; namespace EssentialWF.Activities {   public class Echo : Activity   {     // To illustrate WorkflowInstance.Abort     private int n;     public int NumEchoes     {       get { return this.n; }     }     protected override void Initialize(IServiceProvider provider)     {       n = 0;       Console.WriteLine("Echo.Initialize");       WorkflowQueuingService qService = provider.GetService(         typeof(WorkflowQueuingService)) as WorkflowQueuingService;       WorkflowQueue queue = qService.CreateWorkflowQueue(         this.Name, false);     }     protected override ActivityExecutionStatus Execute(       ActivityExecutionContext context)     {       Console.WriteLine("Echo.Execute");       WorkflowQueuingService qService = context.GetService(         typeof(WorkflowQueuingService)) as WorkflowQueuingService;       WorkflowQueue queue = qService.GetWorkflowQueue(this.Name);       queue.QueueItemAvailable += this.ContinueAt;       return ActivityExecutionStatus.Executing;     }     void ContinueAt(object sender, QueueEventArgs e)     {       Console.WriteLine("Echo.QueueItemAvailable");       ActivityExecutionContext context =         sender as ActivityExecutionContext;       WorkflowQueuingService qService = context.GetService(         typeof(WorkflowQueuingService)) as WorkflowQueuingService;       WorkflowQueue queue = qService.GetWorkflowQueue(this.Name);       object o = queue.Dequeue();       if (o is string)       {         // To illustrate termination via fault         if ((o as string).Equals("fault"))           throw new InvalidOperationException("thrown by Echo");         else if ((o as string).Equals("complete"))         {           queue.QueueItemAvailable -= this.ContinueAt;           context.CloseActivity();           return;         }       }       // else echo the item       Console.WriteLine(o.ToString() + " {" + n++ + "}");     }     protected override void Uninitialize(IServiceProvider provider)     {       Console.WriteLine("Echo.Uninitialize");       WorkflowQueuingService qService = provider.GetService(         typeof(WorkflowQueuingService)) as WorkflowQueuingService;       qService.DeleteWorkflowQueue(this.Name);     }   } } 


Running the application will confirm the behavior of the WF runtime and WF program instances. To begin, the following is the output of the program for a simple run in which we create an instance, start it, send it two strings ("hello" and "goodbye"), and then complete it. For clarity, we are showing the commands issued at the console in boldface:

 create Echo.Initialize WorkflowCreated 14caf2b7-93f3-41ae-87c7-8342fb18ccf5 start WorkflowStarted Echo.Execute WorkflowIdled hello Echo.QueueItemAvailable hello {0} WorkflowIdled goodbye Echo.QueueItemAvailable goodbye {1} WorkflowIdled complete Echo.QueueItemAvailable Echo.Uninitialize WorkflowPersisted WorkflowCompleted NumEchoes=2 


The instance is not automatically persisted when it becomes idle because we used the default value of false for the persistence service's UnloadOnIdle property.

If we call the same set of commands as the previous run except unload instead of complete and then quit the application, the one instance we've created will occupy a row in a database table managed by the SqlWorkflowPersistenceService. The value in the status column of this row is set to 0 to indicate that the instance is still running (in a logical sense, of course, since it is nowhere in memory). We can fire up our application again, and continue the execution of the instance using the get command, which accepts the Guid identifying the instance as a parameter:

 get 919f2445-57a3-492e-a106-3bc619ca2ce9 WorkflowLoaded all done now Echo.QueueItemAvailable all done now {2} WorkflowIdled complete Echo.QueueItemAvailable Echo.Uninitialize WorkflowPersisted WorkflowCompleted NumEchoes=3 


When the instance completes, the value of NumEchoes is reported as 3. The instance retained the value of this data across the persistence point (and the application restart).

The following example shows termination of an instance; the Echo activity responds to an input of fault by throwing an exception:

 create Echo.Initialize WorkflowCreated fbdec9c1-eb3a-413e-93ee-66c3798ddbc8 start WorkflowStarted Echo.Execute WorkflowIdled hello Echo.QueueItemAvailable hello {0} WorkflowIdled fault Echo.QueueItemAvailable Echo.Uninitialize WorkflowPersisted WorkflowTerminated with InvalidOperationException: thrown by Echo 


And finally, here is an example that shows the abort capability of instances:

 create Echo.Initialize WorkflowCreated 5bc7c2ee-ebad-4c99-8386-24d74a297e04 start WorkflowStarted Echo.Execute WorkflowIdled zero Echo.QueueItemAvailable zero {0} WorkflowIdled one Echo.QueueItemAvailable one {1} WorkflowIdled unload WorkflowPersisted WorkflowUnloaded two WorkflowLoaded Echo.QueueItemAvailable two {2} WorkflowIdled abort WorkflowAborted three WorkflowLoaded Echo.QueueItemAvailable three {2} WorkflowIdled complete Echo.QueueItemAvailable Echo.Uninitialize WorkflowPersisted WorkflowCompleted NumEchoes=3 


As you can see, the two input is forgotten (and the in-memory instance is discarded) at the point the abort command is issued. When the next input is provided, the instance continues executing based on the last persisted state. Thus, when the instance completes, NumEchoes is reported as 3 (not 4) because one of the four inputs had been discarded via the abort command.

We encourage you to experiment with this simple application. When you are comfortable with how it works, tweak it. For example, set the value of the UnloadOnIdle property of the SQL persistence service to true and see what changes. Use it to test a custom persistence service, or to run WF programs that are more complicated than a single Echo activity.




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