While creating a process with Windows Workflow Foundation we wanted to communicate between a workflow activity and the workflow application. For this we decided to use Bookmarks and a TrackingParticipant. The idea was that the Activity should create a bookmark to be consumed by a TrackingParticipant which would process something and then pass control back to the Activity.

Following the examples we defined the Activity to derive from NativeActivity and have it create a bookmark by calling CreateBookmark as seen in the following example:

  public sealed class SuspendableActivity : NativeActivity<DictionaryParameters>
  {
    protected override void Execute(NativeActivityContext context)
    {
      // do something ...

      // ... and then create a bookmark to suspend the Activity
      var name = string.Concat(context.WorkflowInstanceId, context.ActivityInstanceId);
      context.CreateBookmark(name, OnResumeBookmark);
    }

    protected override bool CanInduceIdle => true;

    public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
    {
      // resume execution and do something ...
    }
  }

The workflow application registered a TrackingParticipant that would respond to CustomTrackingRecords. The bookmark name used by the Activity and the TrackingParticipant was constructed by the combining the WorkflowInstanceId and the ActivityId:

  public class AppclusiveTrackingParticipant : TrackingParticipant
  {
    protected override void Track(TrackingRecord record, TimeSpan timeout)
    {
      // only process CustomTrackRecord record
      var customRecord = record as CustomTrackingRecord;
      if (null == customRecord)
      {
        return;
      }

      var wfApp = new WorkflowApplication(new SuspendableActivity(), new Dictionary<string, object>());
      var name = string.Concat(record.InstanceId, customRecord.Activity.Id);
      var activityResumeParameters = new DictionaryParameters()
      {
        {"arbitrary-key1", "value1"},
        {"arbitrary-key2", 42L},
      };

      var result = wfApp.ResumeBookmark(name, activityResumeParameters);
      // result now contains "BookmarkResumptionResult.NotFound"
    }
  }

However, after calling ResumeBookmark the Activity was never resumed, and the OnResumeBookmark was never called. Instead the BookmarkResumptionResult always returned NotFound. After having a look at the underlying workflow persistence table it appeared that the Activity to be suspended was not present in the table. Only after the TrackingParticipant returned the Activity could be found in the respective table.

A solution to this problem is to queue the resumption of the bookmark like this:

private class WorkerThreadDto
{
  public string Name { get; set; }

  public DictionaryParameters Map { get; set; }

  public WorkflowApplication WorkflowApplication { get; set; }
}

protected override void Track(TrackingRecord record, TimeSpan timeout)
{
  // ... code as above ...

  var dto = new WorkerThreadDto
  {
    Name = string.Concat(record.InstanceId, "-", customRecord.Activity.Id),
    Map = new DictionaryParameters()
    {
      {"key1", "value1"},
      {"key2", 42L},
    },
    WorkflowApplication = new WorkflowApplication(activityInstance, inputs),
  };

  // dispatching to a asynchronuous thread
  ThreadPool.QueueUserWorkItem(WorkerThread, dto);
}

private static void WorkerThread(object stateInfo)
{
  var dto = stateInfo as WorkerThreadDto;
  Contract.Assert(null != dto);

  // add possible retries in case the Activity has not yet been persisted

  var result = dto.WorkflowApplication.ResumeBookmark(dto.Name, dto.Map);
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.