This post continues my quest to learn more about Windows Workflow and share that experience with you. It assumes knowledge of Windows Workflow. If you don't understand some of the concepts please take a look at one of the books that I mentioned in my first WF post.
One of the operations that we require is the ability to Stop a Build that is in progress. This corresponds to Terminating a workflow. However, in our investigation of how WF handles Terminate, we discovered that it isn't what we really want. Termination of a workflow does not end the workflow immediately. That's not so bad, but it also doesn't give the workflow a chance to clean up anything (i.e. there are no handlers for termination). So, we looked into Cancellation of a workflow instead of using terminate.
We found that it is really hard to cancel a workflow. First of all there is not a WorkflowInstance.Cancel method. So, if you plan to cancel the workflow from outside the instance, you have a hurdle to jump already. But if your workflow is like ours and you can put your own Activity class at the root, you can leap the hurdle pretty easily. What we did was to have the root activity create a Queue (we called it the cancellation queue) with a well known name (a hard coded Guid). From there it seemed obvious that we could Queue an item to this well known Queue and from within the instance call CancelActivity on the root. But alas, that is not allowed. You can only call CancelActivity on the currently executing activity. Bummer! So, we tried cancelling the executing activity, but that ended up just continuing the execution of the next activity after it which wasn't cancelled. Double Bummer!
So, our final solution was to Throw when we got something in the Queue. Throwing automatically causes all activities to cancel including the active one. It also gives the workflow a place to do any clean up that needs to be done by catching the CancellationException at the top of the workflow.
It wasn't pretty, but we were able to accomplish what we wanted. Here is a summary of the steps that actually worked for us:
- In the root level activity (which is our own class derived from one of the WF activities) we create a special Queue just for this situation. The Queue has a constant GUID for the name
- From outside the workflow, we get the WorkflowInstance and Enqueue an item into this special Queue.
- When an item is enqueued, our activity class Throws a special exception. That causes everything to get cancelled.
- Our cancellation handlers can do any clean up.
- We have a special fault handler that also catches this special exception and eats it, so that the workflow completes normally.