Activity Execution Context


Until this point, we have straightforwardly used methods defined on Activity ExecutionContext (AEC), such as GetService and ExecuteActivity, in our activity examples. However, AEC has a larger role in the WF programming model that goes well beyond these methods. Arguably, AEC is overloaded with meanings and capabilities that could usefully be disentangled. Be that as it may, here we will catalog these meanings and capabilities, and provide a conceptual model for understanding and utilizing this key abstraction.

Let's begin with a brief summary of the functions of ActivityExecution-Context that we have already seen exercised.

The first role of AEC is as a container of services that is available to activities during their execution. This set of services is the same for all activities in all WF program instances (for a given application that hosts the WF runtime). Some services are provided by the WF runtime and are always obtainable from AEC; one such service we used in the ReadLine activity is the WorkflowQueuingService. Custom services can be offered by the application that hosts the WF runtime; such services are made available to activities, via AEC, by using the AddService method of WorkflowRuntime.

The fact that ActivityExecutionContext is a provider of services is made explicit by its implementation of the System.IServiceProvider interface, which defines the GetService method. IServiceProvider is a standard way to promote loose coupling between activities and the host application's implementations of services on which those activities depend.

The second role of ActivityExecutionContext is as an API surface through which activities can interact with the (internal) scheduler component of the WF runtime. For example, the ExecuteActivity method requests that a work item (corresponding to the invocation of the Execute method of a child activity) be added to the WF runtime's scheduler work queue. The CloseActivity method requests that the WF runtime finalize the current activity's transition to the Closed state, and resume the internal bookmark that notifies the parent composite activity of the activity's completion. AEC therefore abstracts the internal machinery of the WF runtime; even though we have explained the execution model of the WF runtime in terms of a scheduler and a work queue, these entities are not represented directly in the public API of the WF programming model.

To introduce the third, and subtlest, aspect of AEC, we need to return to the idea of WF program instances as continuations. The execution of a WF program instance is episodic, and at the end of each episodewhen the WF program instance becomes idlethe instance can be persisted in durable storage as a continuation. This continuation, because it represents the entirety of the program instance's state that is necessary for resuming its execution, holds the relevant (internal) WF runtime execution state plus user-defined state, sometimes called the application state. The application state is nothing but the WF program instance's tree of activities (the actual CLR objects), which are usually stateful entities. The runtime state includes the state of the scheduler work queue, WF program queues, and bookkeeping information about internally managed bookmarks (such as subscriptions to the Activity.Closed event).

The resumption point of a bookmark is called an execution handler, so we can refer to the (heap-allocated) execution state required by an execution handler as its execution context. Because an execution handler is typically a method on an activity, we will often refer to this execution context as activity execution context.

ActivityExecutionContext is a programmatic abstraction for precisely this execution context. ActivityExecutionContext is passed to every execution handler either as an explicit argument (as for Activity.Execute) or as the sender parameter in the case of execution handlers that conform to a standard .NET Framework event handler delegate type.

In the examples we've developed thus far in the previous chapters, this "execution context" aspect of AEC has not been apparent. When the host application calls the WorkflowRuntime.CreateWorkflow method, the WF runtime creates a new activity execution context that represents the execution context for the newly created WF program instance. This execution context is managed by the WF runtime because its lifecycle corresponds precisely to the lifecycle of the WF program instance. We call this execution context the default execution context.

The relationship between a WF program instance and its execution context is depicted in Figure 4.1.

Figure 4.1. WF program instance and the default execution context


The WF programming model might have stopped there, with a one-to-one mapping between every WF program instance and a corresponding execution context that is created and managed by the WF runtime. However, this leaves a gap in the execution model for activities.

Instead, the WF programming model allows a composite activity to explicitly create subordinate execution contexts during the execution of a WF program instance. A subordinate execution context, like the default execution context, consists of application state for a set of activities, along with the necessary runtime state. The application state of a subordinate execution context consists of (a copy of) a subtree of activitiesa program fragmentthe root of which is a child activity of the composite activity that created the subordinate AEC.

The API for creating and managing execution contexts is found in the Activity-ExecutionContextManager type, which we will refer to as the execution context manager, or AECManager. The capabilities of AECManager are the basis for a wide range of activity composition patterns, ranging from familiar iterative control flow to more exotic patterns including coroutine style-interleaved iterations, graphs of activities, fork and join patterns, state machines, and activity compensation.

Activity Execution Context Manager

The default execution context for a WF program instance is created and managed internally by the WF runtime. AECManager, which is shown in Listing 4.1, can be utilized by composite activities to explicitly create and manage subordinate execution contexts.

Listing 4.1. ActivityExecutionContextManager

 namespace System.Workflow.ComponentModel {   public sealed class ActivityExecutionContextManager   {     public ActivityExecutionContext CreateExecutionContext(       Activity activity);     public ReadOnlyCollection<ActivityExecutionContext>       ExecutionContexts { get; }     public ActivityExecutionContext GetExecutionContext(       Activity activity);     public void CompleteExecutionContext(       ActivityExecutionContext childContext);     public void CompleteExecutionContext(       ActivityExecutionContext childContext, bool forcePersist);     public IEnumerable<Guid> PersistedExecutionContexts { get; }     public ActivityExecutionContext GetPersistedExecutionContext(       Guid contextGuid);   } } 


AECManager is available to activity execution logic via the Execution-ContextManager property of ActivityExecutionContext, as shown in the following code fragment:

 protected override ActivityExecutionStatus Execute(   ActivityExecutionContext context) {    ActivityExecutionContextManager manager =     context.ExecutionContextManager;    ... 


The role of AECManager is to allow a composite activity to create and manage subordinate execution contexts, which act as boundaries for the execution of subtrees of activities within a WF program. Activity subtrees are essentially program fragments whose roots are child activities of a composite activity within a WF program instance.

A new execution context is created by calling the CreateExecutionContext method of AECManager, passing as a parameter the child activity that is to be the root of the activity subtree that will be represented by the new AEC. This activity is called the template activity of the create operation. The state of the new AEC is essentially a deep copy of the application state of the activity subtree, of which the template activity is the root.

When a new execution context is created, it becomes a member of a collection of active execution contexts that are managed by the current composite activity. This collection is represented by the ExecutionContexts property of AECManager.

The execution state of the activity subtree within the new execution context (which is represented most visibly by the ExecutionStatus and ExecutionResult properties of all the activity objects, but also includes data managed internally by the WF runtime) is guaranteed to be pristine when the CreateExecutionContext method returns. To be specific, the activity objects that form the new instance of the subtree (within the new execution context) each have their Initialize method called so that they properly enter the activity automaton. This is exactly like the initialization of the WF program instance (the WF program's activity tree) that occurs within the default execution context when WorkflowRuntime.CreateWorkflow is called.

In this way, a running WF program instance actually can be viewed, from one vantage point, as a hierarchy (tree) of execution contexts, a simple example of which is shown in Figure 4.2.

Exhibit 4.2. Hierarchy of execution contexts (the default AEC has an id of 1)


All execution contexts except for the default AEC are explicitly created and managed by composite activities, so the execution context hierarchy will always structurally resemble the activity tree of the WF program. A new node in the execution context tree occurs precisely where a composite activity uses the AECManager to create a new execution context.

Rather than drilling deeper into the mechanics of execution context management, we will use some examples of composite activities that leverage AECManager to help bring this aspect of the WF programming model to life.

Iterative Control Flow

None of the composite activities we have encountered so far has had to exhibit iterative behavior. For example, Sequence and Interleave both execute each of their child activities once. However, many control flow constructssuch as the familiar while and foreach statements in C#require repeated execution of a nested (child) statement.

The activity automaton appears to, and in fact does, preclude such repetition because there are no loops present in the state transition diagram. In particular, there is no path that takes an activity from the Executing state to another state and then back again to the Executing state. There are good reasons why the activity automaton has this characteristic, and they should become apparent in the examples we develop.

AECManager provides a composite activity with a way of repeating the execution of a child activity (actually, the subtree of activities rooted at a child activity, which is exactly what is required for iteration). We will use the mechanism to implement control flow constructs such as while.

Before going further, let's clarify terminology. By the term activity iteration (or, simply iteration in the context of WF), we are referring to any circumstance in which an activity within a WF program instance needs to execute more than one time. As a simple example, we need to be able to represent in a WF program, and then of course execute, iterative logic such as that in the following C# code snippet:

 while (expression) {   string s = Console.ReadLine();   Console.WriteLine(s); } 


Provided that we implement a While composite activity with the proper semantics, the representation of this Echo program in XAML is straightforward, as shown in Listing 4.2.

Listing 4.2. WF Program that Uses the While Activity

 <While x:Name="while1" Condition="..." xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow">   <Sequence x:Name="s1">     <ReadLine x:Name="r1" />     <WriteLine x:Name="w1" Text="{wf:ActivityBind r1,Path=Text}" />   </Sequence> </While> 


In Chapter 7, "Advanced Authoring," we will show how the While activity can be associated with validation logic that limits its number of child activities to one. Here we will discuss how the While activity manages the repeated execution of its child activity.

The usual semantics of iteration requires a scoping boundary for data (which pulls along other requirements, such as variable binding rules, which we are ignoring here). This simply means that, within the scope of the construction governing the iteration, a given iteration does not see residual effects of the execution of the prior iterations. Iterations can certainly influence one another via shared data, but that data must be defined outside the scope of the iterative construction. Consider the following C# code:

 while (expression) {   string s = "";   Console.WriteLine(s);   s = Console.ReadLine(); } 


In order to compile this program with the C# compiler, the variable s must be assigned to prior to the WriteLine method call. More to the point, the value assigned to s by the ReadLine method call is lost when the first iteration finishes. The WriteLine will forever write the empty string, no matter what is entered at the console and read by the ReadLine.

The situation changes if the declaration of s occurs outside the while:

 string s = ""; while (expression) {   Console.WriteLine(s);   s = Console.ReadLine(); } 


When s becomes shared data, any iterations of the while loop beyond the first one see the value that is assigned to s during the previous iteration.

In the case of a WF program, the activities (or, rather, the fields of activity objects) are the program's data. So, the WF program of Listing 4.2 must exhibit behavior similar to the C# program in which s is local to the scope within the while. The WriteLine activity's Text property takes its value from the Value property of the ReadLine activity in the current iteration.

If we rewrite the WF program by swapping the order of the WriteLine and ReadLine activities, the WriteLine will forever print the empty string. If this were not the case, there would be unexpected scoping of data and as a result the local maneuverings of one iteration of the While activity would influence (interfere with) the execution of subsequent iterations.

Thus, in order for iterative composite activities to work properly, there must be a way to provide a clean slate of local application data (and execution state) to each iteration. Practically speaking, for a WF program this conceptually amounts to either using (a) a freshly initialized activity subtree for each iteration, or (b) facilitating a reset of the activity subtree used in one iteration for use in the next iteration.

The "reset" approach is problematic. For one thing, if the WF scheduler were used to achieve the reset behavior, the resetting of an activity subtree would unfold asynchronously and necessitate a new "Resetting" state that doesn't correspond to any aspect of a real-world process. For another reason, it would complicate activity development, as activity writers would need to implement potentially difficult reset logic. Properties of an activity can be bound to fields and properties of other activities, so that an activity's reset logic might affect another executing activity adversely. Finally, the reset strategy would preclude (or at least make much more convoluted) certain use cases, including but not limited to interleaved execution of multiple instances of the same child activity, and activity compensation, both of which we will discuss later in this chapter.

The WF programming model therefore equates the normal semantics of a looping construct like while as one use case for subordinate execution contexts. Each iteration of an activity like While is housed within a distinct execution context that holds the state defined by the activity subtree over which the iteration is occurring. This scoping of state ensures the locality of the data, determines its lifetime, and establishes a framework for binding to data in other scopes. We will explore these aspects more fully in the examples to come.

It's time to write the While activity. We will assume that While has an associated validator component that assures only a single child activity is present within the While (this will be developed in Chapter 7).

The While activity is shown in Listing 4.3.

Listing 4.3. While Activity

 using System; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class While : CompositeActivity   {     public static readonly DependencyProperty       ConditionProperty = DependencyProperty.Register(         "Condition",         typeof(ActivityCondition),         typeof(While),         new PropertyMetadata(DependencyPropertyOptions.Metadata)       );     public ActivityCondition Condition     {       get       {         return GetValue(ConditionProperty)           as ActivityCondition;       }       set       {         SetValue(ConditionProperty, value);       }     }     protected override ActivityExecutionStatus Execute(       ActivityExecutionContext context)     {       if (Condition != null && Condition.Evaluate(this, context))       {         ExecuteBody(context);         return ActivityExecutionStatus.Executing;       }       return ActivityExecutionStatus.Closed;     }     void ExecuteBody(ActivityExecutionContext context)     {       ActivityExecutionContextManager manager =         context.ExecutionContextManager;       ActivityExecutionContext newContext =         manager.CreateExecutionContext(EnabledActivities[0]);       Activity newActivity = newContext.Activity;       newActivity.Closed += this.ContinueAt;       newContext.ExecuteActivity(newActivity);     }     void ContinueAt(object sender,       ActivityExecutionStatusChangedEventArgs e)     {       e.Activity.Closed -= this.ContinueAt;       ActivityExecutionContext context =         sender as ActivityExecutionContext;       ActivityExecutionContextManager manager =         context.ExecutionContextManager;       ActivityExecutionContext innerContext =         manager.GetExecutionContext(e.Activity);       manager.CompleteExecutionContext(innerContext);       if ((this.ExecutionStatus ==            ActivityExecutionStatus.Executing) &&           Condition != null &&           Condition.Evaluate(this, context))       {         ExecuteBody(context);       }       else       {         context.CloseActivity();       }     }   } } 


The While activity has a Condition property of type ActivityCondition, and continues to execute iterations of its child activity until the condition evaluates to false. Evaluation of the condition occurs when the While begins its execution and, subsequently, upon the completion of each iteration.

The ActivityCondition type, which is in the System.Workflow.ComponentModel namespace and is shown in Listing 4.4, represents a Boolean expression that can be evaluated by invoking ActivityCondition.Evaluate.

Listing 4.4. ActivityCondition

 namespace System.Workflow.ComponentModel {   public abstract class ActivityCondition : DependencyObject   {     protected ActivityCondition();     public abstract bool Evaluate(Activity activity,       IServiceProvider provider);   } } 


ActivityCondition is useful in other ways during the development of composite activities. For example, branching logic of the if or switch variety can be easily implemented with a composite activity that uses an attached property of type ActivityCondition on its ordered list of child activities. ActivityCondition is an abstract class. In Chapter 8, "Miscellanea," we will discuss the two generic derivatives of ActivityCondition provided by WF. You are also free to write your own custom derivative. If your activities requiring customizable conditional logic reference only the ActivityCondition base class, they are shielded from details of the various condition classes. This lets the user of the activity (the developer of the WF program) decide what is most appropriate for his solution. We can write a simplistic constant condition type that we can use throughout this chapter. This is shown in Listing 4.5.

Listing 4.5. ConstantLoopCondition

 using System; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class ConstantLoopCondition : ActivityCondition   {     int counter = 0;     public static readonly DependencyProperty       MaxCountProperty = DependencyProperty.Register(         "MaxCount",         typeof(int),         typeof(ConstantLoopCondition),         new PropertyMetadata(DependencyPropertyOptions.Metadata)       );     public int MaxCount     {       get { return (int) GetValue(MaxCountProperty); }       set { SetValue(MaxCountProperty, value); }     }     public override bool Evaluate(Activity activity,       IServiceProvider provider)     {       return (counter++ < this.MaxCount);     }   } } 


Now we can rewrite Listing 4.2 by using ConstantLoopCondition. Listing 4.6 is an executable WF program (unlike Listing 4.2) and will loop through the Sequence three times.

Listing 4.6. An Executable Version of Listing 4.2

 <While x:Name="while1" xmlns="http://EssentialWF/Activities" xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">   <While.Condition>     <ConstantLoopCondition MaxCount="3"/>   </While.Condition>   <Sequence x:Name="s1">     <ReadLine x:Name="r1" />     <WriteLine x:Name="w1" Text="{wf:ActivityBind r1,Path=Text}" />   </Sequence> </While> 


Figure 4.3 shows the three execution contexts that are created, one for each of the iterations of the While activity.

Figure 4.3. Three distinct subordinate execution contexts are created for Listing 4.6.


Returning to Listing 4.3, we see that in order to provide local state for each iteration, the While activity must use the AECManager to create a separate execution context for each iteration. The lone child activity of While is the template activity used in the creation of a new execution context. The newly created (subordinate) execution context contains a new instance of the template activity's subtree, and it is the root activity of this fresh subtree that is scheduled for execution. You can see that exactly the same pattern of subscribing to the Closed event of the scheduled activity that we used in Sequence and Interleave also is used here.

The subordinate AEC has an Activity property, which holds the new instance of the template activity. This new instance can be scheduled for execution, using the familiar ExecuteActivity method of AEC, so long as the AEC used to make the request is the execution context that corresponds to the newly created instance of the activity. The basic pattern is illustrated here:

 ActivityExecutionContextManager manager =     context.ExecutionContextManager; Activity template = ... ActivityExecutionContext newContext =   manager.CreateExecutionContext(template); Activity newInstance = newContext.Activity; newInstance.Closed += this.ContinueAt; newContext.ExecuteActivity(newInstance); 


The important line of code is the last one; the newly created activity instance is scheduled for execution within the subordinate AEC.

The IsDynamicActivity property of Activity can be used to determine if a specific activity object is part of the default AEC, or instead has been manufactured as part of the creation of a dynamic execution context. In the preceding code, the newInstance object is a reference to an activity within a subordinate AEC. We can verify this fact by looking at the value of the IsDynamicActivity property:

 System.Diagnostics.Debug.Assert(newActivity.IsDynamicActivity); 


Because the While activity executes a new instance of the template activity for each iteration, the template activity itself (which is part of the default AEC) will always remain in the Initialized state. Only the instances of the template activity, which execute within subordinate execution contexts, move from the Initialized state to the Executing state and then to the Closed state.

This bookkeeping is not terribly difficult for the While activity to manage, but it does play a bit of havoc with the rest of the WF program's view of the While. There is no generalized way for code outside of While to locate the subordinate execution contexts that are dynamically created by While (or the activity objects within these execution contexts).

For this reason, it is a recommended practice for composite activities that dynamically create execution contexts to also provide helper properties or methods (depending upon what makes the most sense for a given composite activity) that assist external code in accessing appropriate iterations of a child activity. In the case of While, there is always at most one active iteration, so we can provide a property (as shown here) that returns the currently executing instance of the template activity:

 public Activity CurrentIteration {   get   {     Activity template = EnabledActivities[0];     Activity[] list = GetDynamicActivities(template);     if (list.Length == 0) return null;     return list[0];   } } 


The GetdynamicActivities method is a protected method defined on the CompositeActivity class. It can be used to obtain the set of active instances of a child activity for which execution contexts have been dynamically created. In the case of While, there will always be at most one, but as we will see in the next section, this might not always be the case. GetdynamicActivities also by its nature implies that separate execution contexts could be created for different child activities (for those composite activities that allow multiple child activities).

Returning again to Listing 4.3, in its ExecuteBody method, the While activity subscribes for the Closed event of the newly created activity instance and schedules this activity for execution in its newly created AEC. As we have seen, the template activity never gets scheduled for execution and always remains in the Initialized state; it acts as a true declaration from which instances are manufactured at runtime. When a dynamic activity instance transitions to the Closed state, the ContinueAt method (of While) will be scheduled. ContinueAt retrieves the subordinate AEC using the GetExecutionContext method of AECManager, and then removes it from the list of active execution contexts by calling CompleteExecutionContext. In other words, when the execution of the activity subtree within a subordinate AEC is completed, the iteration is complete and that AEC can be discarded.

One additional aspect of subordinate execution contexts is related to activity databinding (the mechanics of which are discussed in Chapter 7). ActivityBind objects, though they reference a target activity by name, always implicitly reference an activity within a visible AEC. In other words, even though the instances of activities within the subtrees of different iterations carry the same values for their Name property, there is never any ambiguity about which activity instance an ActivityBind object will use. This is depicted in Figure 4.4.

Figure 4.4. Activity execution context and ActivityBind


The current AEC and parent execution contexts (in the sense of the AEC hierarchy described earlier) are visible (as shown in Figure 4.5), but peer and subordinate AECs are not visible because this would introduce naming ambiguity. This is demonstrated in our example WF program (refer to Listing 4.6) that uses While, in which a property of the WriteLine activity is bound to a property of a peer ReadLine activity. In that example, resolution of the activity databinding expression always uses the ReadLine instance that is present within the same subordinate AEC as the WriteLine.

Figure 4.5. ActivityBind across an activity execution context hierarchy


Interleaved Iteration

The While activity demonstrates how the WF programming model enables scoped (iterative) execution of an activity subtree. Every iteration of While creates a new AEC, in which a new instance of the (template) child activity is executed.

There is nothing stopping us, though, from writing a composite activity that schedules more than one iteration of a child activity for execution at the same time. This does not make sense for While, but it is a quite natural and useful pattern when applied to the C# foreach construction. Instead of processing the items in a collection one after the other (for which use case we could write an ordinary ForEach activity that does what the C# foreach does), our InterleavedForEach activity will schedule the processing of all items in the collection simultaneously. Item processing is performed by a set of separately executed instances of the InterleavedForEach activity's single (template) child activity, with each instance comfortably ensconced within its own AEC.

Listing 4.7 shows an implementation of the InterleavedForEach activity.

Listing 4.7. InterleavedForEach Activity

 using System; using System.Collections; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class InterleavedForEach : CompositeActivity   {     int totalCount = 0;     int closedCount = 0;     public static readonly DependencyProperty       IterationVariableProperty =         DependencyProperty.RegisterAttached(           "IterationVariable",           typeof(object),           typeof(InterleavedForEach)         );     public static object GetIterationVariable(object       dependencyObject)     {       return ((DependencyObject)dependencyObject).         GetValue(IterationVariableProperty);     }     public static void SetIterationVariable(object       dependencyObject, object value)     {       ((DependencyObject)dependencyObject).SetValue(         IterationVariableProperty, value);     }     public static readonly DependencyProperty CollectionProperty =       DependencyProperty.Register(         "Collection",         typeof(IEnumerable),         typeof(InterleavedForEach)       );     public IEnumerable Collection     {       get { return (IEnumerable) GetValue(CollectionProperty); }       set { SetValue(CollectionProperty, value); }     }     protected override ActivityExecutionStatus Execute(       ActivityExecutionContext context)     {       if (Collection == null)         return ActivityExecutionStatus.Closed;       Activity template = this.EnabledActivities[0];       ActivityExecutionContextManager manager =         context.ExecutionContextManager;       foreach (object item in Collection)       {         totalCount++;         ActivityExecutionContext newContext =           manager.CreateExecutionContext(template);         Activity newActivity = newContext.Activity;         InterleavedForEach.SetIterationVariable(newActivity, item);         newActivity.Closed += this.ContinueAt;         newContext.ExecuteActivity(newActivity);       }       if (totalCount == 0)         return ActivityExecutionStatus.Closed;       return ActivityExecutionStatus.Executing;     }     void ContinueAt(object sender,       ActivityExecutionStatusChangedEventArgs e)     {       e.Activity.Closed -= this.ContinueAt;       ActivityExecutionContext context =         sender as ActivityExecutionContext;       ActivityExecutionContextManager manager =         context.ExecutionContextManager;       ActivityExecutionContext innerContext =         manager.GetExecutionContext(e.Activity);       manager.CompleteExecutionContext(innerContext);       if (totalCount == ++closedCount)         context.CloseActivity();     }     public Activity[] ActiveIterations     {       get       {         if (this.EnabledActivities.Count > 0)         {           Activity template = EnabledActivities[0];           return GetDynamicActivities(template);         }         return null;       }     }   } } 


The code for InterleavedForEach is not unlike that of our Interleave activity. These two activities really are doing quite similar things, except that for InterleavedForEach, the number of iterations (number of instances of a single child activity) is determined dynamically (at runtime) by the size of the associated collection. In contrast, the list of child activities of an Interleave is described statically.

Just as with While, we should also implement a helper method on InterleavedForEach that provides external code with access to the currently running iterations. This is shown in Listing 4.8, which shows the implementation of an ActiveIterations property. In the case of the InterleavedForEach, because each iteration is effectively keyed by a data item (that was taken from the Collection property), we could also provide a useful lookup method that accepts this data item (or some representation of it) as a parameter and returns the activity of the appropriate iteration, if it is still executing.

After an iteration completes, the InterleavedForEach activity closes the corresponding subordinate execution context. That iteration's activity object is then no longer an element of the ActiveIterations array.

Listing 4.8. InterleavedForEach.ActiveIterations

 public Activity[] ActiveIterations {   get   {     Activity template = EnabledActivities[0];     return GetDynamicActivities(template);   } } 


One important detail in the implementation of InterleavedForEach has to do with the handoff of data from the collection to an iteration of the template activity. A number of other viable strategies exist, but in this example we are taking a simple approach. The ith item in the collection is given to the ith iteration of the child activity in the form of an attached property, called IterationVariable. The item can then be easily retrieved programmatically, using the standard pattern for attached properties, by activity code within the corresponding AEC:

  Activity a = ...  object o = InterleavedForEach.GetIterationVariable(a); 


The InterleavedForEach activity is a useful construction for modeling real-world control flow. For example, the participants in a document review might be indicated as a list of names. For each participant, a document review task should be assigned and managed until it is completed. In many such situations, the assignees are allowed to complete their tasks simultaneously, making the management of a single task a natural fit for the template activity within an InterleavedForEach. The complexity of managing multiple subordinate execution contexts is entirely hidden from the WF program developer.

One requirement that InterleavedForEach places upon the underlying WF execution model (which we discussed earlier in this chapter) is that peer execution contexts are isolated and cannot interfere with each other. Just as we described earlier for While, each iteration must operate on local data. Stated differently, each iteration executes a local instance of the activity subtree.

The WF runtime enforces that all subordinate execution contexts must be completed (the ExecutionContexts collection is empty) in order for a composite activity to move to the Closed state. This is just an extension of the statement that all child activities of a composite activity must be in the Initialized state or the Closed state in order for the composite activity to move to the Closed state.

Completed Activity Execution Contexts

Our examples so far assume that after a subordinate AEC is completed, it can be discarded. This simple AEC lifecycle is sufficient for the implementation of many composite activities. But it is also possible to serialize a completed AEC for retrieval at a later time. A completed AEC will be serialized (as part of the state of the WF program instance) instead of discarded if you call the overload of CompleteExecutionContext that accepts two parameters, passing TRue as the value of the second parameter. The name of this parameter is forcePersist and, as its name indicates, it determines whether the state of the completed AEC should be saved or not. The overload of CompleteExecutionContext that takes one parameter simply calls the second overload passing a value of false for forcePersist.

A completed AEC will be saved automatically (regardless of the value of forcePersist parameter) if there are successfully executed compensatable activities within the completed activity subtree of that AEC. In order for activity compensation to work, it must be possible to resurrect a previously completed AEC and continue its execution by scheduling its compensation logic. Thus, the potential for compensation overrides a false value for the forcePersist parameter. Compensation will be discussed fully later in this chapter; for now, it is enough to appreciate that compensation logic requires the restoration of a previously completed AEC.

Additional use cases for this advanced feature will be discussed in Appendix B, "Control Flow Patterns."

AEC and WF Program Passivation

Because multiple instances of an activity can execute (each in a different AEC), there is a need to manage the lifecycle of those activity objects in an AEC-specific manner.

The WF runtime will invoke Activity.OnActivityExecutionContextLoad on the activities of a newly created AEC as part of a composite activity's invocation of the AECManager.CreateExecutionContext method. This allows the dynamically created activities within the new AEC a chance to allocate resources specific to the lifetime of that AEC. Similarly, the WF runtime will invoke Activity.OnActivityExecutionContextUnload on the activities in an AEC as part of the AECManager.CompleteExecutionContext method.

Listing 4.9 shows the definition of these methods on the Activity class.

Listing 4.9. AEC Loading and Unloading

 namespace System.Workflow.ComponentModel {   public class Activity   {     protected virtual void OnActivityExecutionContextLoad(       IServiceProvider provider);     protected virtual void OnActivityExecutionContextUnload(       IServiceProvider provider);     /* *** other members *** */   } } 


The execution lifetime of an AEC (including the default AEC) does not necessarily match the in-memory lifetime of the activity objects that are executed within the AEC. Specifically, the lifetime of a subordinate AEC (bracketed by invocation of CreateExecutionContext and CompleteExecutionContext) may span multiple passivation cycles for the WF program instance. Consider again the program of Listing 4.6. Each iteration of the While executes the Sequence activity (and its nested ReadLine and WriteLine activities) in a separate subordinate AEC. Each iteration blocks until the hosting application enqueues an item into the WF program queue that is created by the active instance of the ReadLine activity. Because the scheduler's work queue might become empty at this point, the WF program instance can passivate. The program will be reactivated and resume the execution of the ReadLine activity after the external stimulus arrives.

Upon WF program passivation, the default AEC and all active subordinate execution context (which in the case of While will be at most one) will be saved. As a part of disposing the program instance, the WF runtime will invoke OnActivity-ExecutionContextUnload on the activities in each of the execution contexts, allowing all activity instances to clean up resources allocated for the in-memory lifecycle of the AEC. Correspondingly, the WF runtime will call OnActivityExecutionContextLoad on the activities of all active execution contexts in the program instance each time the program is brought back into memory.

A sequence diagram that shows a single iteration of While is depicted in Figure 4.6.

Exhibit 4.6. AEC lifetime, for a single iteration of the While activity in Listing 4.2





Essential Windows Workflow Foundation
Essential Windows Workflow Foundation
ISBN: 0321399838
EAN: 2147483647
Year: 2006
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net