I've been spending some time digging into Windows Workflow Foundation, in particular looking at how to implement good (and complex) custom activities. One of the topics I've found more interesting is implementing Event Activities, which are those that can be used as the first child activity in an EventDrivenActivity (either on it's own in a state machine workflow or in a branch of a Listen activity). The most obvious example of such an activity is the HandleExternalEvent activity introduced in a previous CTP.
However, as many things in WF, there isn't really much information on the topic of writing complex activities, or what the usual patterns for building such activities are. Particularly in the topic of Event activities, the only documentation I found was the FileSystemEvent activity included in the WF SDK samples.
So after playing for a bit on this topic, I'm going to document what I discovered in a few entries, hopefully someone might find it useful. However, it's very likely I miss somethings (or simply present them wrong), so if anyone can comment on it, I'd sure appreciate it.
The basics
Let's start at the beginning: What makes an event activity different from other custom activities? The answer is that the difference is both behavioral as well as in the contract of the activity in relation to it's parent activity.
An event activity behaves differently at runtime than a different activity. Conceptually, it is different in the sense that it is an activity that is not so much executed at a point in time by the runtime (though it is and can be), but rather than it executes (or does the task it was built to do) when a given event occurrs, which might not be under the control of the workflow runtime or even the actity itself.
For example, the HandleExternalEvent executes in essence when an external data service fires an event. Granted, this is a somewhat artificial distinction because the HandleExternalEvent activity doesn't really do anything else besides receiving notification of the event being fired! We can refine this a little bit and maybe say that an event activity starts execution at a point in time defined by the runtime, and ends its execution at the point in time an external event occurs and is processed by the activity.
Another example would be the Delay activity, whose event is a timer getting fired after a period of time has elapsed.
From a contract perspective, an event activity not only derives from the base Activity class, but also implements two other interfaces: IEventActivity and IActivityEventListener
public interface IEventActivity
{
IComparable QueueName { get; }
void Subscribe(ActivityExecutionContext parentContext,
IActivityEventListener<QueueEventArgs> parentEventHandler);
void Unsubscribe(ActivityExecutionContext parentContext,
IActivityEventListener<QueueEventArgs> parentEventHandler);
}
public interface IActivityEventListener<T> where T : System.EventArgs
{
void OnEvent(object sender, T e);
}
The IEventActivity interface basically allows the parent activity of an event activity (yes, it gets annoying) to subscribe to be notified when the event the activity was built for actually happens, and also unsubscribe from it (meaning it's not interested any longer). In other words, it allows an EventDrivenActivity to ask its child to tell it when the event occurrs so that it can actually execute it and the children of it. The Listen activity, for example, will basically subscribe to both of the event activities in it's branches so that it can know which event occurred first, and basically continue execution along that branch and abandon the other.
The IActivityEventListener interface, however, is actually implemented by however subscribes to an event activity's event, as becomes obvious once you look at the definition of the Subscribe() and Unsubscribe() methods of the IEventActivity interface. Basically, IActivityEventListener is the interface through which subscribers get notified that the event occurred. We'll see later on why your own interface should implement the interface.
Runtime Services
As far as I can see, event activities are not usually implemented in isolation (though I presume they could be in the case of really simple activities). Instead, they are usually implemented with the help of an external service which does the heavy work. Normally, this external service would be a service added to the WorkflowRuntime (usually derived from the base WorkflowRuntimeService class, for convinience).
For example, the HandleExternalEvent activity depends on the presence of the ExternalDataService being initialized and configured in workflow the runtime for it to work. It is actually the ExternalDataService which subscribes to the events of the external object instances and then notifies a given workflow that the event was fired. The FileSystemEvent sample activity in the SDK depends on an external service as well (the FileWatcherService class).
The event activity and its corresponding runtime service will usually communicate in two ways:
- The activity can request operations to the runtime service by simply asking the runtime for the service and invoking methods on it (easy to do via the ActivityExecutionContext, which implements the IServiceProvider interface). This usually is done by the activity either when it is executed or when a subscription is added or removed.
- The runtime service communicates with the activity in the other way by basically notifying it that whatever task it requested of it has been completed. When that happens, you need a reliable way to communicate the particular activity instance (and workflow instance) that something happened in a decoupled and asynchronous fashion, which is accomplished by using the facilities provided by the WorkflowQueuingService that is part of the WorkflowRuntime. So basically, once the task is done, the service will put some data into a WorkflowQueue that the activity created for this purpose and that the activity monitors.
Hope this helps some people get started. In follow up posts, I'll cover some more details as to how it all is put together.