Monday, May 05, 2008

Synchronous execution of a child XOML workflow

Previously I came up with a reasonable approach to executing a child XOML workflow asynchronously but what I was really after was executing the workflow synchronously. I eventually realised that the solution to this problem was to fire up another WorkflowRuntime and execute the workflow using that. I now have a WorkflowExecutor class as below.

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Runtime;
using System.Xml;

namespace WFBuild.Activities
{
  /// <summary>
  /// Executes a workflow synchronously
  /// </summary>
  public class WorkflowExecutor
  {
    private AutoResetEvent waitHandle;
    private Guid wfGuid;
    private Exception ex;

    public void Execute(string fileName)
    {
      WorkflowRuntime workflowRuntime = new WorkflowRuntime();
      workflowRuntime.StartRuntime();
      workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
      workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);

      FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
      XmlTextReader reader = new XmlTextReader(stream);
      WorkflowInstance instance;
      try
      {
        instance = workflowRuntime.CreateWorkflow(reader);
      }
      catch (WorkflowValidationFailedException exp)
      {
        StringBuilder errorMsg = new StringBuilder();
        errorMsg.AppendLine("Validation failed:");
        foreach (ValidationError error in exp.Errors)
        {
          errorMsg.AppendLine(error.ToString());
        }
        throw new Exception(errorMsg.ToString());
      }
      wfGuid = instance.InstanceId;
      waitHandle = new AutoResetEvent(false);
      instance.Start();
      waitHandle.WaitOne();

      if (ex != null)
        throw ex;
    }

    private void workflowRuntime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
    {
      if (e.WorkflowInstance.InstanceId == wfGuid)
      {
        // pass the problem back to the main call
        ex = new Exception("Workflow terminated - " + e.Exception.Message);
        waitHandle.Set();
      }
    }

    private void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
    {
      if (e.WorkflowInstance.InstanceId == wfGuid)
      {
        waitHandle.Set();
      }
    }
  }
}

This code is called from the activity like so

using System.Workflow.ComponentModel;

namespace WFBuild.Activities
{
  public class InvokeXomlWorkflowActivity: Activity
  {
    private string xomlFile;
    public string XomlFile
    {
      get { return xomlFile; }
      set { xomlFile = value; }
    }

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
      WorkflowExecutor executor = new WorkflowExecutor();
      executor.Execute(xomlFile);

      return ActivityExecutionStatus.Closed;
    }
  }
}

Now this still isn't perfect as a general purpose workflow invoker. Ideally the invoker activity wouldn't wait for the child workflow to complete before returning from Execute. It should start the workflow, return with an Executing status, wait for the workflow to complete, then close itself. But this will do for me so that improvement is left as an exercise for the reader.

2 comments:

Anonymous said...

Hej there Doogal!

I just happened to stumble across this blogentry and thought to direct you into a different approach which doesn't require a second WF runtime.

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1076434&SiteID=1

Cheers,
Simon

Doogal said...

Thanks for the heads up Simon. That's a nice approach and looks to handle waiting for the child workflow to finish executing better than my approach. The only thing I don't like is having to add a service to the runtime, if possible I like standalone activities.