InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Workflow Foundation Part III: Participating in persistence

This is the third post about the Workflow Foundation 4. The first two posts can be found here and here.

In the second article I introduced the IncrementExtension which asynchronously increments an integer value and waits one second each time it is called. The activity used bookmarks to stop and resume the workflow while it is running. The problem is this: When the application is forcefully stopped and restarted, the index will start again at zero. This is a shame, since the workflow instance is already persisted to the database between each call.

Enter PersistenceParticipant. This is a base class for extension that which to persist data to the instance store configured for a workflow application. The PersistenceParticipant class has three virtual methods that enable the persistence functionality. Here is the IncrementExtension from the last post extended with the persistence features:

   1: using System;
   2: using System.Activities;
   3: using System.Activities.Hosting;
   4: using System.Activities.Persistence;
   5: using System.Collections.Generic;
   6: using System.Threading;
   7: using System.Xml.Linq;
   8:  
   9: namespace WorkflowConsoleApplication1
  10: {
  11:     class IncrementExtension : PersistenceParticipant, IWorkflowInstanceExtension
  12:     {
  13:         private static XName PersistencePropertyName = XNamespace.Get("urn:example.com:WorkflowDemo/1.0/Increment").GetName("LastValue");
  14:  
  15:         int _Index;
  16:         private WorkflowInstance _Instance;
  17:         Bookmark _Bookmark;
  18:  
  19:         public void Increment(Bookmark bookmark)
  20:         {
  21:             ThreadPool.QueueUserWorkItem(state =>
  22:             {
  23:                 Thread.Sleep(1000);
  24:                 var value = Interlocked.Increment(ref _Index);
  25:                 _Bookmark = bookmark;
  26:                 ContinueWorkflow(value);
  27:             }, null);
  28:         }
  29:  
  30:         private void ContinueWorkflow(int value)
  31:         {
  32:             _Instance.BeginResumeBookmark(_Bookmark, value, CompleteResume, null);
  33:         }
  34:  
  35:         public void CompleteResume(IAsyncResult ar)
  36:         {
  37:             var result = _Instance.EndResumeBookmark(ar);
  38:         }
  39:  
  40:         IEnumerable<object> IWorkflowInstanceExtension.GetAdditionalExtensions()
  41:         {
  42:             yield break;
  43:         }
  44:  
  45:         void IWorkflowInstanceExtension.SetInstance(WorkflowInstance instance)
  46:         {
  47:             _Instance = instance;
  48:         }
  49:  
  50:         protected override void CollectValues(out IDictionary<XName, object> readWriteValues, out IDictionary<XName, object> writeOnlyValues)
  51:         {
  52:             readWriteValues = new Dictionary<XName, object> 
  53:                 {
  54:                     {PersistencePropertyName, new KeyValuePair<Bookmark, int>(_Bookmark, _Index)}
  55:                 };
  56:             writeOnlyValues = null;
  57:         }
  58:  
  59:         protected override void PublishValues(IDictionary<XName, object> readWriteValues)
  60:         {
  61:             var keyvalue = (KeyValuePair<Bookmark, int>)readWriteValues[PersistencePropertyName];
  62:             _Index = (int)keyvalue.Value;
  63:             _Bookmark = (Bookmark)keyvalue.Key;
  64:  
  65:             ContinueWorkflow(_Index);
  66:         }
  67:     }
  68: }

To save data to the instance store, a unique id for the the data, represented by an XName instance. This can be created using the XNamespace class (see line 13). With this unique id, the data of the extension instance can be persisted to the instance store. To save the data the extension needs to override the CollectValues method and add the properties to be persisted to the readWriteValues (lines 50 to 56). The runtime uses the DataContractSerializer to serialize the data. Since .NET 3.5 practically every class that has the SerializableAttribute on its class and/or implements the ISerializable interface can be serialized. At minimum, this method should serialize the bookmark. This is required to continue the workflow when it’s restarted.

When the workflow is restarted the runtime calls the PublishValues method on the extension to load the instance data from the store (line 59-66). This method also should check whether to resume any bookmarks to continue the workflow.

That’s all.


Posted by Henning Krause on Wednesday, October 21, 2009 9:39 PM, last modified on Monday, November 29, 2010 7:49 PM
Permalink | Post RSSRSS comment feed

Workflow Foundation 4 – Part II: NativeActivities and Extensions

This is the second post about Workflow Foundation 4, Beta 2. This time, it’s about native activities and custom extensions. The sample used here is the same as in my last post (Persistence in Workflow Foundation 4). In that post, I used a simple workflow which incremented a integer value by one each time it was executed. To make things more interesting, the custom activity does this in an asynchronous manner.

The activity is a rather simple one, since it uses an extension to do the actual work:

   1: using System.Activities;
   2:  
   3: namespace WorkflowConsoleApplication1
   4: {
   5:     public class IncrementActivity : NativeActivity
   6:     {
   7:         public OutArgument<int> Result { get; set; }
   8:  
   9:         protected override bool CanInduceIdle { get { return true; } }
  10:  
  11:         protected override void Execute(NativeActivityContext context)
  12:         {
  13:             var extension = context.GetExtension<IncrementExtension>();
  14:  
  15:             var bookmark = context.CreateBookmark("test", IncrementCallback);
  16:             extension.Increment(bookmark);
  17:         }
  18:  
  19:         private void IncrementCallback(NativeActivityContext context, Bookmark bookmark, object value)
  20:         {
  21:             Result.Set(context, (int)value);
  22:         }
  23:     }
  24: }

The action has an out argument, the Result property. The value written to this argument is incremented by one each time the activity is called. The key to enable automatic workflow persistence are bookmarks. A short summary about bookmarks can be found in plenty of blogs (for example here). But most of them are wrong with their examples: The syntax has changed quite a bit when it comes to how to create bookmarks. When an activity calls the CreateBookmark method, the runtime automatically stops the workflow instance at that point and persists it to the instance store, if configured. The bookmark takes a name and a callback. The workflow instance is resumed, when the callback is called. In this example, the callback function sets the ‘Result’ argument to the value it receives from the extension.

Note that the activity needs to return true in the CanInduceIdle property. Otherwise, a call to CreateBookmark method will throw an exception.

Once the bookmark has been created, it is handed over to the IncrementExtension.

The IncrementExtension has one method that actually does something: The Increment method. It takes a Bookmark instance as input and resumes it after waiting for 1 second on a different thread:

   1: using System;
   2: using System.Activities;
   3: using System.Activities.Hosting;
   4: using System.Collections.Generic;
   5: using System.Threading;
   6:  
   7: namespace WorkflowConsoleApplication1
   8: {
   9:     class IncrementExtension : IWorkflowInstanceExtension
  10:     {
  11:         int _Index;
  12:         private WorkflowInstance _Instance;
  13:         Bookmark _Bookmark;
  14:  
  15:         public void Increment(Bookmark bookmark)
  16:         {
  17:             ThreadPool.QueueUserWorkItem(state =>
  18:             {
  19:                 Thread.Sleep(1000);
  20:                 var value = Interlocked.Increment(ref _Index);
  21:                 _Bookmark = bookmark;
  22:                 ContinueWorkflow(value);
  23:             }, null);
  24:         }
  25:  
  26:         private void ContinueWorkflow(int value)
  27:         {
  28:             _Instance.BeginResumeBookmark(_Bookmark, value, CompleteResume, null);
  29:         }
  30:  
  31:         public void CompleteResume(IAsyncResult ar)
  32:         {
  33:             var result = _Instance.EndResumeBookmark(ar);
  34:         }
  35:  
  36:         IEnumerable<object> IWorkflowInstanceExtension.GetAdditionalExtensions()
  37:         {
  38:             yield break;
  39:         }
  40:  
  41:         void IWorkflowInstanceExtension.SetInstance(WorkflowInstance instance)
  42:         {
  43:             _Instance = instance;
  44:         }
  45:     }
  46: }

To resume the workflow instance, the extension needs to know which instance it should resume. By default, it does not have this info. It only has an instance of a Bookmark. To get the workflow instance associated with an instance of an extension instance, the extension needs to implement the IWorkflowInstanceExtension. This method is called during the initialization of the extension instance.

Given the instance and the bookmark, the extension can now call the BeginResumeBookmark method on the workflow instance.

One question remains: Where does the extension instance come from? Simple, it is provided during the initialization of the workflow:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         var app = new WorkflowApplication(new Workflow1());
   6:  
   7:         // Specify that the workflow should be persisted everytime it is idle.
   8:         app.PersistableIdle = e => PersistableIdleAction.Persist;
   9:  
  10:         // Add an instance store that is used to persist the workflow.
  11:         app.InstanceStore = new SqlWorkflowInstanceStore("Data Source=(local);Initial Catalog=WorkflowDemo;Integrated Security=True");
  12:  
  13:         app.Extensions.Add(() => new IncrementExtension());
  14:         app.Load(new Guid("1E1A5265-A135-4D42-9D64-2ECCCF22E888"));
  15:         app.Run();
  16:  
  17:         Console.WriteLine("Current Instance id: {0}", app.Id);
  18:  
  19:         Console.ReadLine();
  20:  
  21:         app.Unload();
  22:     }
  23: }

The extension is added to the workflow application in line 13. The app.Extensions.Add method either takes a singleton instance of the extension (which is then used each time the extension is requested) or a factory method which creates a new instance each time the extension is requested.


Posted by Henning Krause on Tuesday, October 20, 2009 1:24 PM, last modified on Monday, November 29, 2010 6:11 PM
Permalink | Post RSSRSS comment feed

Persistence in Workflow Foundation 4 Beta 2

The Workflow Foundation has changed dramatically since .NET 3.5… and many changes have been made even since Beta one. One of the big changes is related to persistence. In earlier versions, either an SqlWorkflowPersistenceService or the and SQLPersistenceProviderFactory was used. Things have become much easier, though, with the release of the second beta of Visual Studio 2010. A workflow can now easily be persisted with a few lines of code. For this sample, I have created a simple workflow which just counts from zero upwards. Since persistence is primarily useful for long-running workflows, I have come up with this example:

image

Obviously, this workflow runs very long… espacially, since the custom IncrementActivity waits one second before incrementing the variable ‘Index’.

To execute the workflow, only a few lines of code are required:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         var app = new WorkflowApplication(new Workflow1());
   6:  
   7:         // Specify that the workflow should be persisted everytime it is idle.
   8:         app.PersistableIdle = e => PersistableIdleAction.Persist;
   9:         
  10:         // Add an instance store that is used to persist the workflow.
  11:         app.InstanceStore = new SqlWorkflowInstanceStore("Data Source=(local);Initial Catalog=WorkflowDemo;Integrated Security=True");
  12:         
  13:         app.Run();
  14:  
  15:         Console.WriteLine("Current Instance id: {0}", app.Id);
  16:         Console.ReadLine();
  17:  
  18:         app.Unload();
  19:     }
  20: }

Sure, there are simpler ways to execute a workflow but AFAIK only this method support persistence using the SqlWorkflowInstanceStore.

To reload the workflow from the database one line needs to be added to the code (in line 12, just before the app.Run() call):

   1: app.Load(new Guid("1E1A5265-A135-4D42-9D64-2ECCCF22E888"));

Of course, the id needs to be replaced with the instance id of the workflow. Once the workflow is running, the instance id can be retrieved from the app.Id property (as shown in line 15).

That’s all.

To persist workflows to a database, the necessary tables and stored procedures must be created in a database. This can easily be accomplished with two files from the .NET 4 runtime directory under %windir%\Microsoft.NET\Framework\v4.0.21006\SQL\en. The SQL statements from the two files SqlWorkflowInstanceStoreSchema.sql and SqlWorkflowInstanceStoreLogic.sql have to be executed against the target database.


Posted by Henning Krause on Monday, October 19, 2009 8:55 PM, last modified on Tuesday, October 20, 2009 1:24 PM
Permalink | Post RSSRSS comment feed