Foundations

Activity Execution Context in Workflows

Matt Milner

Code download available at: Foundations 2007_06.exe(179 KB)

Contents

The Need for ActivityExecutionContext
Workflow Development
Rules and Conditions
Custom Activities
Conclusion

This month I am going to discuss the ActivityExecutionContext (AEC) in Windows® Workflow Foundation. This particular class is a critical component in the execution, persistence, and compensation of workflows and their activities, yet very little attention is paid to it in the documentation and samples. Many of the common problems developers encounter when building workflows and activities are related to how the ActivityExecutionContext works. Whether you are using the While activity, building a state machine workflow, or writing custom activities, it is essential that you know how to work with the AEC. So in this column, I’ll explain the ActivityExecutionContext, show you how to utilize it in your workflows, and discuss how you can use it when writing custom activities.

The Need for ActivityExecutionContext

There are several features of Windows Workflow Foundation that drive the need for (as well as shape the design of) the AEC—these are state management (or persistence), activity re-execution, and compensation. The state management services in Windows Workflow Foundation provide the ability to serialize the entire workflow state and store that information in a durable store so a workflow can be removed from memory and later resumed. In order to support this, there needs to be a container for the state information related to the working set, which includes the activity tree, communication queues, and internal data structures related to scheduling and execution. The activity tree is often referred to as the application state since this is the state created by the application developer and contained in the activities used to define the workflow. The rest of the state is referred to as the runtime state because it is primarily used by the Windows Workflow Foundation runtime to manage workflow execution.

The other two features that require the AEC are closely related. A common design pattern in workflow involves repeating steps. Consider the While activity that executes its child activity repeatedly as long as some condition is met. When you think about the underlying implementation of this execution, there are two main options for handling this. The first involves reusing the activity instances for each iteration and resetting them to their initial state before each execution. Though this might work for something like the While activity, what about the Replicator activity, which can execute its child activity in a parallel? In this case, you might need to have three or four different instances of the activities to be executing at the same time. The reset approach clearly won’t work here.

The second option for executing activities multiple times involves cloning the original activities and then executing the cloned activities for each iteration. In this model, each iteration of the activities will start with the same template—the initial state of the activities—and each cloned instance can be managed separately. One of the benefits of this model is that each clone can have its own state managed by the runtime, and this won’t interfere with the other clones. Each of these clones requires its own AEC to manage the state for that instance of the activities.

Compensation allows for an activity to do some work after it has already completed its main execution path. This lets an activity handle cases where errors in the process occur after the activity has completed and allows activities to return to a state consistent with the starting state. This might mean updating systems or data that have already changed. Basically, compensation requires that a set of actions taken by an activity can be accounted for after the activity has already completed its work. In order to enable this, the state for that work must be able to be saved and restored. In order to compensate for the changes made by a particular instance, and not some other instance, the activity must be able to have state managed for each iteration. Thus, use of the AEC for each iteration of a set of activities helps to enable compensation by allowing an activity to gain access to the correct state that it used when executing.

The AEC acts as the container for the runtime and application state, and each workflow will have one default AEC for the root activity (the workflow itself) and may also have child AECs if activities create any. For example, if you have a workflow with a single While activity and that While activity has a single Code activity, as shown in Figure 1, then each iteration of the While activity will create a child AEC.

Figure 1 ActivityExecutionContexts with a Simple While Activity

Figure 1** ActivityExecutionContexts with a Simple While Activity **(Click the image for a larger view)

Workflow Development

A common problem that developers encounter is trying to write code in an event handler when the activity appears in a repeating activity. For example, suppose you have a series of activities within a While activity and you want to reference one from an event handler of another activity, as shown in Figure 2. For purposes of demonstration, I will use the Code activity and a custom ReadLine activity that reads input from the console. The ReadLine activity has a single property that exposes the text that was entered at the console. In this sample, I want to access the Text property of the ReadLine in the ExecuteCode event handler of the Code activity.

Figure 2 Event Handlers

Figure 2** Event Handlers **

Your first thought may be that I am going to write code that accesses the ReadLine activity directly:

Console.WriteLine(“Text from ReadLine2: {0}”, readLine2.Text);

However, if I do this and run the workflow, I get output that looks like that shown in Figure 3. The problem is that I am referencing the ReadLine activity from the template and not the instance that was cloned for this iteration of the While activity. One common approach to address this involves gaining a reference to the current cloned instance of the activities in order to reference the correct cloned instance of the target—in this case, the ReadLine activity.

Figure 3 Missing Output with a Direct Reference

Figure 3** Missing Output with a Direct Reference **(Click the image for a larger view)

In order to get a reference to the correct tree of cloned activities in an event handler, you can use the sender of the event, which will be the activity the event is defined on. In my sample, this is the Code activity. From there, I can navigate the activity tree to find the activity I am interested in. This approach is shown in the following code (I can use this code in place of the previous code to get the outcome I desire):

CodeActivity thisActivityInstance = sender as 
  CodeActivity;
CompositeActivity parent = 
  thisActivityInstance.Parent as CompositeActivity;

MSDNMag.WFActivities.ReadLine target = 
  (MSDNMag.WFActivities.ReadLine) 
  parent.GetActivityByName(“readLine2”);
Console.WriteLine(
  “Text from ReadLine in this AEC: {0}”, target.Text);

In this example, I get a reference to the instance of the Code activity I am working with and navigate via the parent activity to the correct child activity. Then I can access that particular instance to get the value of the Text property from this instance.

Another option is to use shared data between activities. This would allow an activity that is executing in a child AEC to access data from the default AEC—that of the workflow itself. In my example, I would define a string variable on the workflow class and then bind the Text property of the ReadLine activity to this property. This would ensure that when the ReadLine is executed, this property would be set with the correct value. The Code activity could then reference the data on the workflow which would be up to date. Using this model, I avoid the issue of the cloned activities and referencing the template as I am referencing the single instance of the workflow.

This approach works well for the While activity, where only a single iteration is active at a given time, but this approach gets more complicated when you start executing activities in parallel using the Replicator, ConditionedActivityGroup, or a custom activity. In that case, the shared state would need to be managed in a keyed collection of some sort, which would then require managing a key as well.

The cleanest and, I would argue, best solution is to use a custom activity and activity binding. For my simple example, I could use the custom WriteLine activity and bind its Text property to the Text property of the ReadLine activity. When you use activity binding within an AEC, the target activity is correctly resolved to the cloned instance within the same AEC. This provides a simpler model for connecting your activities and getting the results you expect.

Each of these three models for working with AECs is shown in the sample code available for this column on the MSDN® Magazine Web site. The AECSamples workflow contains three While activities, each showing one of the described methods for getting the results you expect.

State Machine workflows often cause problems similar to those described in the previous example for similar, though not always apparent, reasons. State machine workflows are intended to model process execution based on a set of changing states. The power of this model is that the process, or the thing that it models, can enter a given state more than once, providing a more flexible model than sequential workflow. Because of this design, it is possible that a given State activity can be executed more than once and each iteration must therefore be executed in a separate AEC. This means each State activity acts as a template and is cloned and run in a new AEC each time the state is entered. In addition, the State activity also runs each of the EventDriven activities it contains in a separate AEC. The same solutions I described earlier can be used in the state machine: activity tree traversal, shared state, and activity binding. The AECStateMachine sample workflow in the code download provides a concrete example.

Rules and Conditions

Another area where developers struggle with the AEC is when writing rules or conditions. Activity conditions are used to make decisions in a workflow, such as when you define the condition for the IfElse activity. There are two types you can usually choose from: CodeCondition and DeclarativeRuleCondition. The CodeCondition allows you to write some code in an event handler that sets a Boolean flag indicating the outcome of the condition check. A DeclarativeRuleCondition references a rule condition statement, which also evaluates to a Boolean value.

When using a CodeCondition within an AEC (an IfElse activity within an EventDriven activity in a state machine, for example), you see the same issues discussed previously with referencing activities in the AEC. A CodeCondition that tries to directly reference the ReadLine activity that begins the steps in an EventDriven activity will be accessing the template instead of the currently executing activity. After all, a code condition uses delegates just like the event handlers on an activity. For example, you might try to write this code to evaluate your condition and return True if the user enters "quit" on the console.

if (readLine1.Text != null && readLine1.Text.StartsWith(“quit”))
{
    e.Result = true;
}

Unfortunately, the Text property will always be null as the readLine1 instance is the template and a cloned instance will be used for each execution of the State. I can solve this problem using shared state at the workflow level or by getting access to the current activity tree using the sender parameter and navigating up and back down the tree:

//get the grandparent activity (the eventdriven)
CompositeActivity grandparent = (sender as Activity).Parent.Parent;

//figure the result by finding the child ReadLine activity
e.Result = ((MSDNMag.WFActivities.ReadLine)
    grandparent.GetActivityByName(
        “readLine1”)).Text.StartsWith(“quit”);

Note that I accessed the grandparent and not the parent activity. In the case of the IfElse activity, the condition is actually invoked by an IfElseBranch activity, which is a child of the IfElse activity. If I had used the parent, I would have been incorrectly referencing the IfElse activity and not the EventDriven. In this case, I would never find the readLine1 activity with the GetActivityByName method. My point is that when you use this model of reference resolving, you need to know which activity is acting as the sender so that you can properly traverse the activity tree.

The more challenging problems usually occur when you attempt to use the DeclarativeRuleCondition within a child AEC or you try to use the Policy activity to execute a set of rules. Rule conditions and policies are authored against the workflow class itself so the rules must be written in a way to gain access to the current dynamic instance of the activities that you reference in your rules. Being able to write rules of this type often depends on the capabilities exposed by the activity that created the child AEC. You could easily make the mistake of writing a typical condition for a declarative rule as:

this.readLine2.Text.StartsWith(“quit”)

This is similar to the incorrect code in the previous example when using the code condition.

Now you might be thinking that I can get around this problem by using the GetActivityByName method to get the current instance I want:

((MSDNMag.WFActivities.ReadLine)this.GetActivityByName(
    “AECRulesInitialState”).GetActivityByName(
        “eventDrivenActivity1”).GetActivityByName(
            “readLine2”)).Text.StartsWith(“quit”)

Unfortunately, this provides identical results, as it gets the exact same templates instead of the current executing instance. In order to get at the currently executing clone of the activities, you must depend on the capabilities exposed by the activity that created the new AEC. For example, the State activity exposes a method called GetDynamicActivity, which allows you to query for the currently executing instance of a child activity. Using this method, I can now write a declarative rule that correctly identifies the current instance of the ReadLine so I can make my decision correctly:

((MSDNMag.WFActivities.ReadLine)
    ((System.Workflow.Activities.StateActivity)
        this.GetDynamicActivity(
            “AECRulesInitialState”)).GetDynamicActivity(
                 “eventDrivenActivity1”).GetActivityByName(
                     “readLine2”)).Text.StartsWith(“quit”)

In this declarative rule, I first use the GetDynamicActivity method to get the currently executing instance of the AECRulesInitialState state activity. I then cast the result to a State activity, which allows me to use the GetDynamicActivity method again to access the currently executing instance of the EventDriven activity. Once I have a reference to the EventDriven activity, I no longer have to worry about any child AEC so I can use the GetActivityByName method to get a reference to the ReadLine activity and its Text property.

This same approach can be used with other activities in both state machine and sequential workflows. For example, the While activity has a property named DynamicActivity, which can be used to access the current clone of the activity template in event handlers or declarative rule conditions. The code download includes the state machine sample as well as a sequential workflow showing the use of the While activity.

Custom Activities

The issues I’ve discussed so far are relevant to all workflow developers and are essential to your ability to create workflows. Activity authors, however, also have to understand when and how to create and manage AECs when building composite activities.

When determining whether your custom composite activity needs to create a new AEC, ask yourself, "will my activity execute its child activities more than once?" If the answer is yes, then you need to create a new AEC for each iteration of the child activities. Consider two activities included in the base activity library of Windows Workflow Foundation: Parallel and While. I have already shown you that the While activity iteratively executes its child activity and must therefore run each iteration in a new AEC. The Parallel activity allows you to define multiple branches of execution and schedules each branch to run in an interleaved fashion. Even though the Parallel activity is executing multiple sequences of child activities, it is only executing each child one time. Even if the definitions of two branches appear identical, they are separate instances of the activity types and they execute independently.

To examine the process of writing this type of activity, I will create a ForEach activity that will iterate over a collection of data objects and execute its child activity once for each item in the collection. The full sample code is available in the code download for this column, but here I will focus on the code specifically related to managing the AECs.

First, I need three dependency properties to help manage state and data: CurrentIteration (a private property to manage the index), Items (the collection of data to be iterated over), and DataItem (an attached property to pass the item from the collection to the child activity being executed). There is nothing special about these properties related to the AEC; they are simply the state you need to manage to execute multiple iterations of the child activity.

Next, you need to work on the Execute method of the ForEach activity, which is responsible for starting the execution of the child activity:

if (EnabledActivities.Count == 0 || Items == null || Items.Count == 0)
    return ActivityExecutionStatus.Closed;

CurrentIteration = 0;

ExecuteIteration(executionContext);

return ActivityExecutionStatus.Executing;

It is important to quickly check to make sure there is work to do in the form of a child activity and some collection of data to iterate over. If not, the activity can close and execution can move on to the next activity in the workflow. If there is data and a child activity, then the iteration index is initialized and I call the ExecuteIteration method, which is where the work starts to get interesting. Finally, because I am scheduling the child to execute, I return a status of Executing.

The ExecuteIteration method is responsible for managing the creation of a new AEC in order to run the child activity:

Activity act = EnabledActivities[0];

ActivityExecutionContext newContext = 
    parentCtx.ExecutionContextManager.CreateExecutionContext(act);

newContext.Activity.RegisterForStatusChange(
    Activity.ClosedEvent, this);
newContext.Activity.SetValue(
    ForEach.DataItemProperty, Items[CurrentIteration]);

newContext.ExecuteActivity(newContext.Activity);

The first step is to create a new ActivityExecutionContext using the ActivityExecutionContextManager (AECManager). The AECManager is available from any AEC and is the object used to control the creation and completion of child AECs. When the new AEC is created, the AECManager clones the activity that was passed in and uses the new activity when executing. That is why, in the following steps, I use the Activity property on the new AEC to refer to the activity when registering for status changes and asking the context to execute the activity. The only other item of note here is that I set the DataItemProperty on the child activity to the current data from the collection so the data is available to the dynamically executing activity.

Notice that to create the AEC, I pass in an activity that will act as the root activity in the AEC. This is an important concept to understand as it guides the design of your activity when you know that you need to create a new AEC based on a single activity. Most activities that create a new AEC, like the While and Replicator activities, only allow a single child activity. At first glance, this can seem like a limitation, but you can easily add a Sequence activity as the single child and then add as many children to the Sequence as needed. There are two steps to ensuring that you have a single child activity. The first is to create a custom validator, which ensures that your activity only has one child. The second is to create a designer, which does not allow more than one activity to be added to the ForEach. Both of these techniques are included in the sample code for the ForEach activity. For more discussion on designers and validators, see my December 2006 article "Windows Workflow: Build Custom Activities to Extend the Reach of your Workflows".

The final step of managing the AEC is to handle the Closed event of the child activity in order to complete the context. The sender parameter to this delegate is the AEC for my composite activity and can be used to control the activity and to get access to the AECManager in order to manage child AECs. The ActivityExecutionStatusChangedEventArgs contains a reference to the activity that has closed, which can then be used to get the correct AEC for that activity using the GetExecutionContext method of the AECManager. Once I have a reference to the AEC for the activity, I close that AEC using the AECManager. It is important that, in addition to all of the child activities being closed, the child AECs all get completed. Once the AEC has been completed, I can check to see if I have more data items. If so, I start another iteration and if not, I close my activity:

ActivityExecutionContext ctx = sender as ActivityExecutionContext;
            
e.Activity.UnregisterForStatusChange(Activity.ClosedEvent, this);
            
ActivityExecutionContext childContext = 
    ctx.ExecutionContextManager.GetExecutionContext(e.Activity);
ctx.ExecutionContextManager.CompleteExecutionContext(childContext);

if (++CurrentIteration >= Items.Count) ctx.CloseActivity();
else ExecuteIteration(ctx);

The CompleteExecutionContext method has an overload that takes a Boolean parameter to indicate if the workflow state should be persisted at this point. By default, this value is False. However, if any child activities that implement the ICompensatable interface have completed, the AEC will be persisted regardless of the value passed for this parameter. This overload allows you to provide input as to whether the AEC needs to be persisted based on the needs of your activity and whether the state will need to be available later.

The last thing to do in my activity is to support the ability of other code to get a reference to the current instance of the child activity being executed. To do that, I add a DynamicActivity property on the ForEach activity and use the GetDynamicActivities method from the CompositeActivity base class. This method takes in an activity and returns an array of the dynamic instances of that activity. In this case, there will only ever be one dynamic instance at a time, so I can always return the first item in the array:

public Activity DynamicActivity
{
    get 
    {
        if (EnabledActivities.Count == 0) return null;
        Activity[] activities = 
            GetDynamicActivities(EnabledActivities[0]);
        if (activities.Length > 0) return activities[0];
        return null;
    }
}

It is important to keep in mind that other aspects of composite activity development might also involve managing the AEC—cancellation and fault handling for example. In these cases, the pattern for accessing the correct AEC is the same as shown here and the steps you take will depend upon your activity.

Conclusion

It is critical that workflow developers understand how the AEC works and when it is necessary to consider using the AEC. Likewise, it is essential for developers to know how to code workflows, activities, and rules in order to deal with the AEC. Writing a simple custom composite activity is a good way to get an understanding of how other activities may be managing their AEC. I recommend this as a learning technique for most workflow developers.

Send your questions and comments to  mmnet30@microsoft.com.

Matt Milner is an independent software consultant specializing in Microsoft technologies including .NET, Web services, Windows Workflow Foundation, Windows Communication Foundation, and BizTalk Server. As an instructor for Pluralsight, Matt teaches courses on Workflow, BizTalk Server, and Windows Communication Foundation. Matt lives in Minnesota with his wife Kristen and his two sons.