Introduction
Presumably you have read these articles or similar tutorials:
In addition to crafting codes, you can also develop new activity through declarative programming with Workflow/XAML.
Using the Code
The source code is available at https://github.com/zijianhuang/WorkflowDemo.
Prerequisites:
- Visual Studio 2015 Update 1 or Visual Studio 2013 Update 3
- xUnit (included)
- EssentialDiagnostics (included)
- Workflow Persistence SQL database, with default local database WF.
Examples in this article are from a test class: WorkflowApplicationTests.
WaitForSignalOrDelay
Activity WaitForSignalOrDelay can wait for an incoming bookmark call or a duration of delay.
Wakeup
public sealed class Wakeup : NativeActivity
{
public Wakeup()
{
}
public InArgument<string> BookmarkName { get; set; }
protected override bool CanInduceIdle
{
get
{
return true;
}
}
protected override void Execute(NativeActivityContext context)
{
string name = this.BookmarkName.Get(context);
if (name == null)
{
throw new ArgumentException(string.Format("ReadLine {0}: BookmarkName cannot be null", this.DisplayName), "BookmarkName");
}
context.CreateBookmark(name);
}
}
This is a simplest bookmark activity without needing bookmark value and return.
Compose through XAML
So you just need 2 blocking activities: Wakeup and Delay running in parallel through Pick. Either activity gets waked up, the Sequence will go through.
To make the workflow available as a component, 2 InArguments are defined. And the Result is true for wakeup call, and false for duration expiration.
Tests
[Fact]
public void TestWaitForSignalOrDelayWithBookmarkToWakup()
{
var bookmarkName = "Wakup Now";
var a = new WaitForSignalOrDelay()
{
Duration = TimeSpan.FromSeconds(10),
BookmarkName = bookmarkName,
};
AutoResetEvent syncEvent = new AutoResetEvent(false);
bool completed1 = false;
bool unloaded1 = false;
IDictionary<string, object> outputs = null;
var app = new WorkflowApplication(a);
app.InstanceStore = WFDefinitionStore.Instance.Store;
app.PersistableIdle = (eventArgs) =>
{
return PersistableIdleAction.Unload;
};
app.OnUnhandledException = (e) =>
{
return UnhandledExceptionAction.Abort;
};
app.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
completed1 = true;
outputs = e.Outputs;
syncEvent.Set();
};
app.Aborted = (eventArgs) =>
{
};
app.Unloaded = (eventArgs) =>
{
unloaded1 = true;
syncEvent.Set();
};
var id = app.Id;
app.Run();
syncEvent.WaitOne();
Assert.False(completed1);
Assert.True(unloaded1);
outputs = LoadWithBookmarkAndComplete(a, id, bookmarkName, null);
Assert.True((bool)outputs["Result"]);
}
[Fact]
public void TestWaitForSignalOrDelayAndWakupAfterPendingTimeExpire()
{
var a = new WaitForSignalOrDelay()
{
Duration=TimeSpan.FromSeconds(10),
BookmarkName="Wakeup",
};
AutoResetEvent syncEvent = new AutoResetEvent(false);
bool completed1 = false;
bool unloaded1 = false;
var app = new WorkflowApplication(a);
app.InstanceStore = WFDefinitionStore.Instance.Store;
app.PersistableIdle = (eventArgs) =>
{
return PersistableIdleAction.Unload;
};
app.OnUnhandledException = (e) =>
{
return UnhandledExceptionAction.Abort;
};
app.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
completed1 = true;
syncEvent.Set();
};
app.Aborted = (eventArgs) =>
{
};
app.Unloaded = (eventArgs) =>
{
unloaded1 = true;
syncEvent.Set();
};
var id = app.Id;
app.Run();
syncEvent.WaitOne();
Assert.False(completed1);
Assert.True(unloaded1);
WFDefinitionStore.Instance.TryAdd(id, a);
Thread.Sleep(5000);
var outputs = LoadAndCompleteLongRunning(id);
Assert.False((bool)outputs["Result"]);
}
Use the WaitForSignalOrDelay activity/workflow in another workflow
WaitForSignalOrAlarm
This class is very similar to the WaitForSignalOrDelay, and is crafted through C# coding. And the activity will continue upon a specific DateTime, rather than after a duration.
public sealed class WaitForSignalOrAlarm : Activity<bool>
{
public InArgument<DateTime> AlarmTime { get; set; }
public InArgument<string> BookmarkName { get; set; }
public WaitForSignalOrAlarm()
{
Implementation = () =>
new Pick()
{
Branches = {
new PickBranch
{
Trigger = new Wakeup()
{
BookmarkName=new InArgument<string>((c)=> BookmarkName.Get(c))
},
Action = new Assign<bool>()
{
To= new ArgumentReference<bool> { ArgumentName = "Result" },
Value= true,
}
},
new PickBranch
{
Trigger = new Delay
{
Duration = new InArgument<TimeSpan>((c)=> GetDuration(AlarmTime.Get(c)))
},
Action = new Assign<bool>()
{
To=new ArgumentReference<bool> { ArgumentName = "Result" },
Value= false,
}
}
}
};
}
static TimeSpan GetDuration(DateTime alarmTime)
{
if (alarmTime < DateTime.Now)
return TimeSpan.Zero;
return alarmTime - DateTime.Now;
}
}
Points of Interest
So you have seen examples of crafting activities through deriving Activity, CodeActivity and NativeActivity, as well as dynamically constructing a DynamicActivity instance. You might have noticed the Implementation property. However, the MSDN does not document well about this property in Activity derived classes.
CodeActivity.Implementation says this is not supported. Correct.
NativeActivity.Implementation says this iss the execution logic. However, this is not really supported, as you can try, or review the source code of NativeActivity.
While both WaitForSignalOrDelay and WaitForSignalOrAlarm are derived from class Activity for composite activities, MSDN has an example of doing similar things through deriving from NativeActivity. While this MSDN example demonstrates what already supported by DynamicActivity, you seem to be able to craft WaitForSignalOrDelay through deriving from NativeActivity, using similar design of the MSDN example. However, it will looks clumsy to me, because of low level APIs and more complicated algorithm. If you have different ideas, please leave a comment.