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 CustomTrackingRecord
s. 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); }