Jul 7, 2009

WF Programming Model

Workflow is queue and scheduler.

 public class ReadLine : Activity
  {
    private string text;
    public string Text
    {
      get { return text; }
    }

    protected override ActivityExecutionStatus Execute(
      ActivityExecutionContext context)
    {
      WorkflowQueuingService qService =
        context.GetService();

      WorkflowQueue queue =
        qService.CreateWorkflowQueue(this.Name, true);
      queue.QueueItemAvailable += this.ContinueAt;

      return ActivityExecutionStatus.Executing;
    }

    void ContinueAt(object sender, QueueEventArgs e)
    {
      ActivityExecutionContext context =
        sender as ActivityExecutionContext;

      WorkflowQueuingService qService =
        context.GetService();

      WorkflowQueue queue = qService.GetWorkflowQueue(this.Name);
      text = (string) queue.Dequeue();
      qService.DeleteWorkflowQueue(this.Name);
      context.CloseActivity();
    }
  }

In WF, the data structure chosen to represent a bookmark's capacity to hold data is queue. This queue, which we shall call a WF program queue is created by ReadLine using WorkflowQueuingService.

namespace System.Workflow.Runtime
{
  public class WorkflowQueuingService
  {
    //  queueName is the bookmark name

    public WorkflowQueue CreateWorkflowQueue(
      IComparable queueName, bool transactional);
    public bool Exists(IComparable queueName);
    public WorkflowQueue GetWorkflowQueue(IComparable queueName);
    public void DeleteWorkflowQueue(IComparable queueName);

    /* *** other members *** */
  }
}

The WorkflowQueue object that is returned by the CreateWorkflowQueue method offers an event, QueueItemAvailable. Despite the syntactic sugar of the C# event, this event represents the asynchronous delivery of stimulus from an external entity to an activity, and is exactly the same pattern of bookmark resumption. The more refined WF version of the programming model for bookmarks allows a bookmark's payload (a WF program queue) to hold an ordered list of inputs that await processing (instead of a single object as did the bookmark in Chapter 1). The physical resumption point of the bookmark is still just a delegate (ContinueAt) even though in the WF programming model the delegate is indicated using the += event subscription syntax of C#.

namespace System.Workflow.Runtime
{
  public class WorkflowQueue
  {
    public event EventHandler QueueItemAvailable;

    public object Dequeue();

    public int Count { get; }
    public IComparable QueueName { get; }

    /* *** other members *** */
  }
}

The return value of the ReadLine activity's Execute method indicates that, at that point in time, the ReadLine has pending bookmarks; its execution is not complete. When an item is enqueued in its WF program queue, perhaps days after the ReadLine began its execution, the bookmark is resumed and, as a result, the ContinueAt method is invoked. After obtaining the item from its queue and setting the value of its text field, the ReadLine activity reports its completion.

public class Sequence : CompositeActivity
  {
    protected override ActivityExecutionStatus Execute(
      ActivityExecutionContext context)
    {
      if (this.EnabledActivities.Count == 0)
        return ActivityExecutionStatus.Closed;

      Activity child = this.EnabledActivities[0];
      child.Closed += this.ContinueAt;
      context.ExecuteActivity(child);

      return ActivityExecutionStatus.Executing;
    }

    void ContinueAt(object sender,
      ActivityExecutionStatusChangedEventArgs e)
    {
      ActivityExecutionContext context =
        sender as ActivityExecutionContext;

      e.Activity.Closed -= this.ContinueAt;
      int index = this.EnabledActivities.IndexOf(e.Activity);

      if ((index + 1) == this.EnabledActivities.Count)
        context.CloseActivity();

      else
      {
        Activity child = this.EnabledActivities[index + 1];
        child.Closed += this.ContinueAt;
        context.ExecuteActivity(child);
      }
    }
  }

Sequence cannot directly execute its child activities since the Activity.Execute method has accessibility of protected internal. Instead, Sequence requests the execution of a child activity via ActivityExecutionContext.

Sequence subscribes to the Activity.Closed event before it requests the execution of a child activity. When the child activity completes its execution, the execution of the Sequence is resumed at the ContinueAt method. The Sequence activity's subscription to the Closed event of a child activity is syntactic sugar for the creation of a bookmark that is managed internally, on behalf of Sequence, by the WF runtime.

The ActivityExecutionContext type is effectively an activity-facing abstraction on top of the WF runtime.

namespace System.Workflow.ComponentModel
{
  public class ActivityExecutionContext : System.IServiceProvider
  {
    public void ExecuteActivity(Activity activity);
    public void CloseActivity();

    public T GetService();
    public object GetService(Type serviceType);

    /* *** other members *** */
  }
}