WF4 Workflow Episodes
I remember at PDC08 when Kenny Wolf was introducing WF4 he said a workflow is kind of like a college student. Most of the time it lays around sleeping (and drinking beer) and occasionally it wakes up (when an assignment is due) and does a pulse of work and then goes back to sleeping and drinking beer. Kenny called this a “pulse of work”, I like to call it a Workflow Episode.
When you run a Workflow the activities will run until one of the following happens
- The workflow completes
- The workflow aborts because of an unhandled exception
- The workflow becomes idle
When you write code that uses Workflow Application you provide delegates that are called as the workflow moves through this cycle of events. Sooner or later you realize that your code builds up an understanding (a “contract” if you will) with the workflow that you are running. There is a dialog of sorts that goes on between the host application and the workflow where the host runs the workflow and then waits for idle events to do the next thing.
The more I thought about this pattern the more I wanted a way to make it simpler. So I began to imagine what I wanted from the API and I realized that I wanted a construct that didn't exist yet so I created it by providing extensions to the WorkflowApplication type in the Microsoft.Activities.dll project. This project is an experiment in providing a Task based API for WorkflowApplication. There are no guarantees or promises about this ever becoming part of the product in the future. However we did want to share it with you
My goal for the API was simple.
- Provide a way to run an episode of work synchronously or asynchronously using a Task
- Capture the results of the episode into a result object
- Timeout if the episode doesn't complete in time
- Throw exceptions when bad things happen
How To Use It
- Download the latest release of Microsoft.Activities
- Add a reference to it from your project
- Add using Microsoft.Activities
- Extension methods will now appear
Test Scenarios
I wish I had more time to create samples and docs to help you with this. One of the best ways to learn how the API works is by looking at the unit tests.
Scenario: Run a Workflow with No Bookmarks
In this scenario we simply run a workflow that completes. You could do this with WorkflowInvoker today. The advantage here is that there is one model for invoking workflows that works the same way for bookmarks or no bookmarks
Given
- An activity with no bookmarks
When
- RunEpisode is invoked
Then
- The calling thread is blocked until the workflow completes
- And a WorkflowCompletedEpisodeResult is returned
- With output arguments
- And state = ActivityInstanceState.Closed
1: [TestMethod]
2: public void WhenNoBookmarksAndOutArgRunShouldCompleteWithOutArgs()
3: {
4: const int Expected = 1;
5: var workflowApplication = new WorkflowApplication(new EchoArg<int> { InputValue = Expected });
6:
7: // To run it synchronously
8: var result = workflowApplication.RunEpisode(this.DefaultTimeout);
9:
10: // Or asynchronously using a Task
11: // var result = workflowApplication.RunEpisodeAsync(this.DefaultTimeout).Result;
12:
13:
14: Assert.IsInstanceOfType(result, typeof(WorkflowCompletedEpisodeResult));
15:
16: var completedResult = (WorkflowCompletedEpisodeResult)result;
17: Assert.AreEqual(ActivityInstanceState.Closed, completedResult.State);
18: AssertOutArgument.AreEqual(completedResult.Outputs, "Result", Expected);
19: }
Scenario: Run a Workflow With Bookmarks
Given
- An activity which will set a bookmark named “Test”
When
- RunEpisodeAsync is invoked with the bookmark name “Test”
Then
- The calling thread is blocked until the workflow becomes idle with a bookmark named “Test”
- And a WorkflowIdleEpisodeResult is returned
- And state = ActivityInstanceState.Executing
- And 1 bookmark is pending
1: [TestMethod]
2: public void WhenActivityWithBookmarkGoesIdleRunAsyncShouldReturnWorkflowIdleEpisodeResult()
3: {
4: var workflowApplication = new WorkflowApplication(new TestBookmark<int> { BookmarkName = "Test" });
5:
6: var result = workflowApplication.RunEpisodeAsync("Test", this.DefaultTimeout).Result;
7:
8: Assert.IsInstanceOfType(result, typeof(WorkflowIdleEpisodeResult));
9:
10: var idleEpisodeResult = (WorkflowIdleEpisodeResult)result;
11: Assert.AreEqual(ActivityInstanceState.Executing, idleEpisodeResult.State);
12: Assert.AreEqual(1, idleEpisodeResult.IdleArgs.Bookmarks.Count);
13: }
Try It
There are many, many more tests of the API included in the source for Microsoft.Activities so I encourage you to take a look and see what you think.
Note: Microsoft.Activities.UnitTesting previously had a pre-cursor to this API. Now that I’ve added it to WorkflowApplication I removed the API from Microsoft.Activities.UnitTesting (yes this is a breaking change) Changing to the new API was not too difficult – see the unit tests for examples of what to do.