AJ's blog

October 2, 2007

Workflow Communication

Filed under: .NET, .NET Framework, C#, Software Architecture, Software Development, WF — ajdotnet @ 8:36 pm

Note: This is part of a series:
1. Talking WF (introduction)
2. Workflow Instance State Management
more to come (hosting, error management).

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.

Some activities already provide design time support for services and the activities for data exchange are among them (HandleExternalEventActivity and CallExternalMethodActivity):

activity2  prop2

activity1   prop1

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.

A silly interface pattern

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.

[ExternalDataExchange]
interface IVactionRequestWorkflow
{
    // call into workflow instance
    event EventHandler<ConfirmationEventArgs> ConfirmVacationRequest;
    
    // callback from workflow instance
    void ConfirmationProcessed(bool confirmed);
}

Silly, isn’t it?

Some threading issues

communication1To 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:

  • communication2The 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)
            ConfirmVacationRequest(null, ea);
    }
    
    public void ConfirmationProcessed(bool confirmed)
    {
        _confirmationProcessed.Set();
    }    public void SynchronousConfirmVacationRequest(WorkflowInstance instance, bool confirmed)
    {
        // send event
        OnConfirmVacationRequest(new ConfirmationEventArgs(instance.InstanceId, confirmed));
        // wait for callback
        _confirmationProcessed.WaitOne();
    }
}

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.

That’s all for now folks,
AJ.NET

kick it on DotNetKicks.com

Advertisement

2 Comments »

  1. Sehr interessanter Artikel. Was mir nicht ganz klar wird ist das Design sync-on-async. Wenn Sie eine Webanwendung bauen, die Workflows mit dem obigen Service verwendet, ist es nicht ein fragwürdiges Design, einen Thread der Webanwendung zu blockieren, bis die Verarbeitung abgeschlossen ist? Zumal die workflow runtime hinsichtlich des Scheduling eine Black box zu sein scheint. Es kann sein, dass die Workflowinstanz den Request sofort bearbeitet, vielleicht sogar auf dem selben Thread(?), es kann aber auch sein, dass es dauert. Wie lange, wer kann das wissen? Gibt es nicht bessere Patterns, solche Situationen speziell in Webanwendungen, die ja hochgeradig skalierbar sein sollen, abzubilden?

    Comment by Tim — October 3, 2007 @ 10:09 pm

  2. @Tim: Der allgemeine Fall (ohne Synchronisierung) ist ja out-of-the-box abgebildet, wenn ich keine explizite Snychronisierung über ein AutoResetEvent mache. Der hier unterstellte Use Case ist aber gerade: Ich habe eine Liste von Daten, ich stoße eine Verarbeitung an und ich möchte mit dem Page Refresh sehen, daß das übernommen wurde (alleine schon, damit der Anwender die Verarbeitung nicht nochmal anstößt). Und ich unterstelle außerdem, daß das sehr zeitnah passiert. Hier ist Synchronisierung sinnvoll, die Alternative wäre Pollen oder Hoffen.
    Fragwürdig wird das ganze sicher dann, wenn diese Verarbeitung längere Zeit benötigt. In diesem Falle würde ich die hier beschriebene Synchronisierung auf das Entgegennehmen das Auftrags beschränken (also einen künstlichen Zwischenzustand “ist gerade in Verarbeitung” einführen). (Diese Variante wird später in der Serie nochmal aufgegriffen.)

    HIH,
    AJ.NET

    Comment by ajdotnet — October 4, 2007 @ 7:05 am


RSS feed for comments on this post. TrackBack URI

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

Blog at WordPress.com.

%d bloggers like this: