01 August 2007
I wrote the following for the Using CSLA .NET 3.0 ebook, but I don’t think I’m going to use it now, because I’ve wrapped most of this into a new class in CSLA .NET. Rather than letting this go to waste though, I thought I’d post it here. Remember that it is just draft content, so it may have typos or errors, but perhaps it will be useful to someone:
Basic Workflow Execution
Executing a workflow is a little tricky, because workflows default to running on a background thread. That means you must take steps to ensure that the workflow completes before the host process terminates.
One way to solve this issue is to always execute a workflow synchronously. Another is to use a thread synchronization object to prevent the process from terminating until the workflow completes.
Note: It is also possible to suspend and resume workflows, and even to unload them from memory so they store their state in a database. Later you can reload that workflow instance and resume it. These advanced scenarios are outside the scope of this book
Synchronous Execution
The code to synchronously execute a workflow follows a standard pattern:
1.      Create an instance of the 
WorkflowRuntime
.
2.      Create a synchronization object.
3.      Set up event handlers.
4.      Create workflow instance.
5.      Ensure you have a valid principal object.
6.      Start the workflow.
7.      Wait for the workflow to complete.
The only step unique to CSLA .NET is number 5, and that is only required if you are using custom authentication. The WF runtime will automatically ensure that the background thread that executes the workflow has the same principal as the thread that calls the workflow’s 
Start()
 method, but you must ensure that the principal is set on the current thread before calling 
Start()
.
The following code implements these steps to execute the 
ProjectWorkflow
 implemented earlier in this chapter:
      using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
      {
        Exeception error = null;
<?xml:namespace prefix = o ns = “urn:schemas-microsoft-com:office:office” /> 
        AutoResetEvent waitHandle = new AutoResetEvent(false);
        workflowRuntime.WorkflowCompleted += 
          delegate(object sender, WorkflowCompletedEventArgs e) 
          {
             waitHandle.Set(); 
          };
        workflowRuntime.WorkflowTerminated += 
          delegate(object sender, WorkflowTerminatedEventArgs e)
          {
            error = e.Exception;
            waitHandle.Set();
          };
 
        // create workflow instance
        Dictionary<string,object> parameters = new Dictionary<string,object>();
        parameters.Add("ProjectId", projectId);
        WorkflowInstance instance = 
          workflowRuntime.CreateWorkflow(
            typeof(PTWorkflow.ProjectWorkflow), 
            parameters);
 
        // login before starting WF instance
        ProjectTracker.Library.Security.PTPrincipal.Login("pm", "pm");
 
        // execute workflow
        instance.Start();
 
        // wait for workflow to complete
        waitHandle.WaitOne();
 
        // throw any workflow exception
        if (error != null)
          throw error;
      }
Creating the workflow instance involves setting up a 
Dictionary<string, object>
 that contains name/value pairs for any parameters to be passed into th workflow instance:
        // create workflow instance
        Dictionary<string,object> parameters = new Dictionary<string,object>();
        parameters.Add("ProjectId", projectId);
        WorkflowInstance instance = 
          workflowRuntime.CreateWorkflow(
            typeof(PTWorkflow.ProjectWorkflow), 
            parameters);
The name in the dictionary must correspond to the name of a dependency property defined by the workflow, and of course the type of the value must match the dependency property type.
Keep in mind that creating an instance of a workflow does not start the workflow. The workflow won’t start executing until the 
Start()
 method is called later in the code.
The 
waitHandle
 synchronization object is the key to making this process synchronous. The 
waitHandle
 object starts out unset because 
false
 is passed to its constructor as its initial state:
        AutoResetEvent waitHandle = new AutoResetEvent(false);
At the bottom of the code is a line that calls 
WaitOne()
, thus blocking the current thread until 
waitHandle
 is set:
        // execute workflow
        instance.Start();
 
        // wait for workflow to complete
        waitHandle.WaitOne();
While the current (starting) thread is blocked, the workflow is busy executing on a background thread. In other words, the 
Start()
 call returns immediately, having just started the workflow instance executing on a background thread. Without the 
WaitOne()
 call, the current thread would exit the code block, which would dispose the WF engine instance while it is executing the workflow. The result would be an exception.
Notice how the event handlers for the 
WorkflowCompleted
 and 
WorkflowTerminated
 events both call 
waitHandle.Set()
. These events are raised by the WF engine when the workflow either completes or terminates unexpectedly. Either way, by calling the 
Set()
 method, the current thread is released so it can continue running.
In the case of a workflow terminating unexpectedly, the exception from the workflow is made available to the 
WorkflowTerminated
 event handler. You can choose what to do with this information as appropriate for your application. One technique is shown here, which is to store the 
Exception
 object in a field:
          delegate(object sender, WorkflowTerminatedEventArgs e)
          {
            error = e.Exception;
            waitHandle.Set();
          };
And then have the current thread throw the exception once it is unblocked:
        // wait for workflow to complete
        waitHandle.WaitOne();
 
        // throw any workflow exception
        if (error != null)
          throw error;
The result of this code is that the workflow appears to run synchronously, even though it really executes on a background thread.
Asynchronous Execution
Allowing a workflow to run asynchronously is just a slightly more complex version of running the workflow synchronously. The important thing is to ensure that your process doesn’t exit until the workflow is complete. This means that the synchronization object must be available at a broader scope so you can write code to prevent the application from closing if the workflow is still running.
You also must come up with a way to deal with any exception object in the case that the workflow terminates unexpectedly. One solution is to elevate the 
error
 field from the previous example to a broader scope as well.
Finally, the 
WorkflowRuntime
 instance must remain in memory until the workflow completes.
This means that you must define these fields so they exist at an application level, for instance using 
static
 fields:
  private static AutoResetEvent _waitHandle = null;
  private static Exception _workflowError = null;
  private static WorkflowRuntime _workflowRuntime = null;
Then you can create a method to start the workflow:
    public static void BeginWorkflow(Guid projectId)
    {
      _workflowRuntime = new WorkflowRuntime();
      _waitHandle = new AutoResetEvent(false);
      _workflowRuntime.WorkflowCompleted += 
        delegate(object sender, WorkflowCompletedEventArgs e) 
        {
           _waitHandle.Set(); 
        };
      _workflowRuntime.WorkflowTerminated += 
        delegate(object sender, WorkflowTerminatedEventArgs e)
        {
          _workflowError = e.Exception;
          _waitHandle.Set();
        };
 
      // create workflow instance
      Dictionary<string,object> parameters = new Dictionary<string,object>();
      parameters.Add("ProjectId", projectId);
      WorkflowInstance instance = 
        _workflowRuntime.CreateWorkflow(
          typeof(PTWorkflow.ProjectWorkflow), 
          parameters);
 
      // login before starting WF instance
      ProjectTracker.Library.Security.PTPrincipal.Login("pm", "pm");
 
      // execute workflow
      instance.Start();
    }
Notice that the 
WorkflowRuntime
 object is no longer in a 
using
 block, so it can remain in memory, not disposed, while the workflow instance is running on the background thread.
The workflow instance is created the same as before, and its 
Start()
 method is called. At that point this method simply ends, returning to the caller.
Once you call 
BeginWorkflow()
 the workflow is started on a background thread, but your current thread (often the UI thread) is free to continue working.
The final piece to the puzzle is a method your application can call before it exits, or when it otherwise can’t continue without the workflow having completed:
    public static void EndWorkflow()
    {
      // wait for workflow to complete
      _waitHandle.WaitOne();
 
      // dispose runtime
      _workflowRuntime.Dispose();
 
      if (_workflowError != null)
        throw _workflowError;
    }
It is important to realize that this method will block the current thread until 
_waitHandle
 is set. If the workflow completes before this method is called, then 
_waitHandle
 is already set, and this method runs immediately, but if the workflow is still running, this method will block until the workflow completes or terminates.
For this to work, you must call 
EndWorkflow()
 before your process terminates to properly dispose the runtime and to determine if the workflow terminated unexpectedly.