Workflow as Activities
If you have programmed with Windows Workflow Foundation (WF), you should be very familiar with Activities. In WF, programs are defined by a tree of Activities. Some Activities are used to control the flow of program, like Sequence, If, and While; some Activities perform specific actions, like Assign, InvokeMethod and WriteLine; some could communicate with external systems, like Send and Receive.
For example this is a simple workflow:
Figure 1. A simple "toss up" workflow
Having bells and whistles removed, this workflow is actually a tree of Activities:
Figure 2. Activity tree for workflow in Figure 1
Activities are basic modeling units for WF, like Abstract Syntax Tree (AST) for textual programing languages. Since WF uses a runtime to execute the Activity tree directly, Activities are also basic execution units for WF, like IL for CLR.
However Activities have some unique attributes make it more powerful than traditional AST or IL:
- Activities are fully extensible. Anyone could build an Activity and define its syntax plus semantics. Sometimes such Activities are called “custom Activities”, to distinguish them from Activities shipped in WF. Personally I think it’s a bad name, because there are no thing special about “built-in” Activities like Sequence and If, except they happen to be developed by Microsoft and shipped in .Net Framework. WF Runtime has no special knowledge about built-in Activities at all. As a matter of fact, any developer could write his/her own version of Sequence and If using public Activites API, use them in a workflow and they should work just fine.
- Activities are self-describable. An Activity defines its own metadata and execution logic. From WF Runtime’s point of view, an Activity is just a piece of data associated with self-defined behavior. A WF program is just a description of such a tree of data. So WF programing is purely declarative and data driven. WF also provides a rich set of APIs to inspect and create Activity trees, similar to reflection and reflection emit in CLR. With those APIs, it’s very easy for developers to create new programming experience for WF. An obvious example is visual designers. WF team has shipped a set of designers which could be used inside or outside of Visual Studio. It’s not hard for anyone else to build new visual designers or even textual language to produce Activity trees as WF program as well.
As of today, the number of “built-in” Activities shipped in .Net framework is very small. To build any real world application, developers would need to write “custom” Activities to perform their tasks. Samples of such tasks include:
- A control flow to model certain business logic, like group review where a request has to be reviewed and agreed by multiple people before it could be approved.
- An activity that automates some human task, like sending out an Email or running an Admin script.
- A bridge to connect to other systems, like an Activity to talk to a Payroll system using vendor specific communication protocol.
Now we know that Activities are core of Workflow programming experience. WF developers have to understand Activities really well. So let’s put Activities to center of the stage to talk about WF's Activity model.
WF provides several classes for developers to implement Activities with different needs.
Figure 3. Activity modeling class hierarchy
At root of the hierarchy is Activity, which is base class for all Activities in WF. To define a new activity, a developer would needs to implement a concrete class of Activity or one of its predefined subclasses which provide different authoring style:
Besides being base class and providing all basic properties/ methods for all activities, the Activity class could also be used directly to define an activity using composition. When subclassing directly from Activity, a developer describes an Activity‘s Implementation declaratively by creating a tree of existing Activities. For example:
class Greet : Activity
() => new Sequence
Text = "Hello"
Text = "World"
Code 1. Sample usage of Activity class
This code tells WF Runtime that when Greet is running, it needs to create a Sequence Activity with two WriteLine Activities inside, and execute that Sequence Activity as Greet’s body.
When programming using this class, developers could not directly access WF Runtime’s functionalities like bookmark. But it’s very straightforward and fully declarative. We expect this to be the main approach to create new composite Activities once there are rich Activity libraries built by the community.
CodeActivity allows authoring Activities whose logic could not be built using existing Activities, but simple enough to be implemented by code in one single Execute method.
Here is an example,
class PrintFile : CodeActivity
protected override void Execute(CodeActivityContext context)
using (StreamReader file = new StreamReader("foo.txt"))
while ((line = file.ReadLine()) != null)
Code 2. Sample usage of CodeActivity class
Using CodeActivity is simple, just implement its Execute method and this method will be called when this Activity is executed. CodeActivity is very useful to create simple leaf Activities.
One of the main values of WF is to support long running and asynchronous services. It’s very common to build an Activity to perform a long running operation involving waiting for other system’s input. We should not write blocking code in one Execute method in WF because it will block the Runtime from doing any other work and the service will have scalability and availability problems. As we discussed in pervious blog, WF has Continuation built in to solve this problem. In general, Activities need to use a set of bookmark APIs to take advantage of WF continuation. However to save developer’s effort, AsyncCodeActivity provides a high-level encapsulation of a very common continuation pattern: execution logic could be implemented by one async method call. The encapsulation follows .Net's BeginInvoke pattern and hides bookmarking so developers could easily do async programming in WF without learning new concept. It’s perfect to implement simple Asynchronous IO in WF. We will give an example in 2nd installment of this blog.
If an Activity ‘s logic could not be expressed as composition of existing Activities, nor could it be implemented by one method (or one async method), you would need to use NativeActivity. For example, some complicated control flow pattern like State Machine or Peri Net fall into this category. When programming with NativeActivity, developers need to think at the same level of WF Runtime: they need to view Activity execution as scheduling workitems in a queue; and they have direct access to most of Runtime scheduling features like bookmarks, canceling, error progragating, and etc. Using NativeActivity has steeper learning curve and programming is much more complicated. However it's the most powerful activity authoring method and it’s a good window to look into WF Runtime. We will try to build a NativeActivity in continuation of this blog and we will look deeper into it in a series of future blogs about WF Runtime.
There are cases where you would want an Activity to "return" some value. For example, Activities which retrieve some data from a file or DB need to pass the data to rest of workflow. WF defined a series of generic class for that purpose. You will find that for any Activity class we talked above, there’s a generic counterpart. For example, Activity<T> is very similar to Activity in the way it supports composition style of Activity authoring. But it has one more output argument (we will need to have a separate blog talk about WF’s argument concept) whose name is Result and type is T. Activity<T>’s implementation could populate this argument during execution and its value will be available for other Activities in same workflow.
CodeActivity<T>, AsyncCodeActivity<T>, and NativeActivity<T> all have similar story so there is no need to go over them one by one.