The WorkflowApplication is a great way to execute your workflows in process. Usually the fact that the WorkflowApplication is asynchronous is a great thing but there are cases when a little more synchronous execution is nice. For example executing a workflow and updating the state of the user interface is much simpler when the WorkflowApplication.Run() doesn’t finish until all work is done.
The key to creating a synchronous WorkflowApplication is using its SynchronizationContext. Normally you set this to SynchronizationContext.Current so everything executes on the current UI thread. However this is still an asynchronous call and the Run doesn’t block.
Take this very simple workflow and its execution output.
var workflow = new Sequence()
{
Activities = {
new WriteLine(){ Text="Workflow is running"}
}
};
var app = new WorkflowApplication(workflow);
Console.WriteLine("Before WorkflowApplication.Run()");
app.Run();
Console.WriteLine("After WorkflowApplication.Run()");
As you can see the Run() didn’t block and the message after the Run() was printed before the message from the workflow.
Executing the workflow synchronously
Making this workflow execute in a synchronous fashion is easy and requires only a very small change by setting the SynchronizationContext to a custom implementation.
var workflow = new Sequence()
{
Activities = {
new WriteLine(){ Text="Workflow is running"}
}
};
var app = new WorkflowApplication(workflow);
app.SynchronizationContext = new SynchronousSynchronizationContext();
Console.WriteLine("Before WorkflowApplication.Run()");
app.Run();
Console.WriteLine("After WorkflowApplication.Run()");
The SynchronousSynchronizationContext used is real simple. The workflow internals always call the Post() method to execute work, so all we need to do is just execute the delegate passed with the state passed.
class SynchronousSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
d(state);
}
}
Executing an asynchronous workflow
Using this same SynchronousSynchronizationContext with an asynchronous workflow works just fine. If I add the following simple bookmarked activity to the workflow and execute it I get the following result:
The code to execute the workflow is as follows
var workflow = new Sequence()
{
Activities = {
new WriteLine(){ Text="Workflow has started"},
new MyBookmarkedActivity(),
new WriteLine(){ Text="Workflow is done"}
}
};
var app = new WorkflowApplication(workflow);
app.SynchronizationContext = new SynchronousSynchronizationContext();
app.Idle = e => Console.WriteLine("WorkflowApplication.Idle called");
Console.WriteLine("Before WorkflowApplication.Run()");
app.Run();
Console.WriteLine("After WorkflowApplication.Run()");
Console.WriteLine();
Console.WriteLine("Before WorkflowApplication.ResumeBookmark()");
Console.WriteLine("ResumeBookmark: {0}", app.ResumeBookmark("MyBookmark", null));
Console.WriteLine("After WorkflowApplication.ResumeBookmark()");
And the bookmarked activity is as follows:
class MyBookmarkedActivity : NativeActivity
{
protected override bool CanInduceIdle
{
get { return true; }
}
protected override void Execute(NativeActivityContext context)
{
Console.WriteLine("Creating bookmark");
context.CreateBookmark("MyBookmark", BookmarkResumed);
}
private void BookmarkResumed(NativeActivityContext context, Bookmark bookmark, object value)
{
Console.WriteLine("Bookmark resumed");
}
}
Conclusion
A simple yet effective addition to the workflow runtime for that special case where the asynchronous behavior is not quite what you want but the WorkflowInvoker is not flexible enough
Enjoy!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu