The last post brought up the following topic:
“I clicked a button and somehow the workflow took over and did something. As you know, this happens asynchronously, nevertheless I saw the correct state after the processing instantly. To make that work, the web application has to have a means to know when the state has changed. Workflow communication.”
So, how do you talk to a workflow instance? How do you know it processed your request? How does it talk back?
Please note: Talking to a workflow is a topic that is actually well documented. Yet it still has its pitfalls and it takes a little getting used to. Therefore I’ll recap the pattern, trying to explain it along the line. And I will assume a certain use case (the synchronization issue) because it showed up regularly in our use cases.
If you are new to WF you’ll have to master two or three things to talk to a workflow: The service pattern, a silly interface pattern, and some threading issues.
The service pattern
If you read an article about WF, the service pattern is most likely not introduced properly; most articles simply say “do this, and you’ll accomplish that”. The service pattern has been widely used for visual studio design time support but that’s the only usage I am aware of. Until WinFx came around, that is. Firstly WF uses the service pattern for runtime services such as persistence. In this case the developer only has to announce existing service implementations. Secondly it also employes services for data exchange — in which case the developer has to deal with the service pattern in all its beauty.
For the record: The service pattern usually defines a service as an interface. It decouples the consumer of a service and the provider. A certain service may be optional (such as the tracking service) or mandatory (such as the scheduler service). If the consumer needs a particular service, it’ll ask the provider. Usually providers form a chain, so if the first provider does not know the service, it’ll ask the next one.
And no, it’s got nothing to do with web services…
You might want to read Create And Host Custom Designers With The .NET Framework 2.0 Chapter “Extensibility with Services” for a slightly more elaborate description.
For data exchange the developer has to define an interface (the service contract) and a class that implements the interface (the service). During startup he creates an instance of the service and registers it with the data exchange service (a service provider in itself). If you need it in an activity to do something worthwhile you ask the ActivityExecutionContext (the service provider at hand, see Activity.Execute) for the service. Then you work with it.
Please note that many of the predefined activities offer other anchors to attach your own code, events and the like, that do not provide easy access to the ActivityExecutionContext, thus no service provider is readily available.
When I first wanted to talk to a workflow, I knew I need an interface. To call into the workflow I expected a method; to get notified from the workflow I expected an event. Of course I expected to call and be called on my own thread. Guess what… .
Well, my attitude actually was “I – the code of the host application – create the workflow, I’m in charge, I define the point of view.” The reality of course is “I – the workflow – do not care what the host application code thinks who he is. I do the work, I’m in charge, I define the point of view.” This turns my initial expectations upside down. In other words: A call into the workflow instance notifies the instance of some external demand, thus it is made via an event. If the workflow instance has to say something it straightly calls a method. And it does not care what thread is involved.
// call into workflow instance
event EventHandler<ConfirmationEventArgs> ConfirmVacationRequest;
// callback from workflow instance
void ConfirmationProcessed(bool confirmed);
Silly, isn’t it?
Some threading issues
To elaborate more on the threading issue: We know that WF is inherently multithreaded. When you call into a workflow instance (pardon, when you signal it), the WF engine (more precisely the data exchange service) will catch the event on your thread, do some context switch magic, and signal the workflow instance on its thread. (It might have to load the workflow instance, which is just another reason for the decoupling.) If the workflow instance on the other hand has some information it calls your code on its own thread — the WF engine can hardly hijack your thread for some automatic context switch. Consequence: You signal the workflow instance and the call (more or less) immediately returns. But you don’t know when the workflow will actually get signaled, much less when it will have done the respective processing. You may issue a call from the workflow instance but you will have to care for the context switch and the proper reaction yourself.
This is pretty good for performance and scalability. But if you need synchronous behavior (as in our example) it hurts a bit.
To make a long story short, here’s the pattern we came about with:
- The interface contains a CallIntoWorkflowInstance event and a respective CallIntoWorkflowInstanceAcknowledge callback method (ConfirmVacationRequest and ConfirmationProcessed in the interface above)
- The workflow instance accepts the CallIntoWorkflowInstance event, does some work (supposed to be synchronous) and calls CallIntoWorkflowInstanceAcknowledge to tell you it finished its part.
- The service implements the event (along with a SignalCallIntoWorkflowInstance helper method) and the method. It also has an AutoResetEvent for the synchronization.
- The helper method (called from the web application thread) raises the event and afterwards waits for the AutoResetEvent.
- The callback method (called from the workflow instance thread) simply signals the AutoResetEvent. This will cause the helper method on the other thread to be unblocked and to return.
The service implementing the above interface consequently looks like this:
class VacationRequestWorkflowService : IVactionRequestWorkflow
AutoResetEvent _confirmationProcessed = new AutoResetEvent(false);
public event EventHandler<ConfirmationEventArgs> ConfirmVacationRequest;
public void OnConfirmVacationRequest(ConfirmationEventArgs ea)
if (ConfirmVacationRequest != null)
public void ConfirmationProcessed(bool confirmed)
} public void SynchronousConfirmVacationRequest(WorkflowInstance instance, bool confirmed)
// send event
OnConfirmVacationRequest(new ConfirmationEventArgs(instance.InstanceId, confirmed));
// wait for callback
Now we have synchronized the workflow instance with out calling application. Of course we rely on the callback or we would wait endlessly, but that’s another story.
The next post will take a closer look at hosting.