Flylib.com

Books Software

 
 
 

The Activity Execution Context


The Activity Execution Context

The activity execution model is encapsulated in a class called ActivityExecutionContext . An instance of this class is passed to each method that needs access to an activity execution context, most notably the Activity.Execute method. The following code shows the metadata for this class:



namespace System.Workflow.ComponentModel { public sealed class ActivityExecutionContext : IServiceProvider, IDisposable { public static readonly DependencyProperty CurrentExceptionProperty; public Activity Activity { get; } public Guid ContextGuid { get; } public ActivityExecutionContextManager ExecutionContextManager { get; } public void CancelActivity(Activity activity); public void CloseActivity(); public void ExecuteActivity(Activity activity); public T GetService<T>(); public object GetService(Type serviceType); public void TrackData(object userData); public void TrackData(string userDataKey, object userData); } }


The Activity property provides access to the activity instance in the current execution context. Remember that every time an activity executes, a new instance is created. Therefore, an activity instance on a workflow definition is not the same as the Activity property of the ActivityExecutionContext class. The following code illustrates this concept:



if (MyWorkflow.Activities[0] == context.Activity) { // this will never happen }


The ActivityExecutionContext class also exposes a property that returns an instance of the Activity ExecutionContextManager class. Basically, this class enables you to create new execution contexts for an activity’s children. You do this by calling the CreateExecutionContext method, which takes an activity instance from the workflow definition. In addition, the ActivityExecutionContextManager class allows you to flag an already-created execution context as completed. Because execution contexts can be persisted when a persistence service is present, you can use the ActivityExecutionContextManager class to obtain access to persisted, as well as nonpersisted, contexts. To obtain a reference to a persisted execution context, you must have access to its ContextGuid . (This class is covered again shortly in a development example.)

The ActivityExecutionContext class also exposes methods that control an activity’s execution. CloseActivity causes the activity associated with the context to close. The CancelActivity and ExecuteActivity methods control child activities, which is why they take an Activity instance as a parameter.



Developing an Iterative Activity

To illustrate the concepts related to activity execution contexts, the following code shows a sample activity that behaves like the C# foreach construct. Activities that possess the properties of a loop are referred to as iterative activities. The ForEachActivity class has a property of type IEnumerable , which means that basically any .NET collection can be provided for iteration. This activity was modeled after the out-of-the-box While activity, and like the While activity, the sample implements the IActivityEventListener <ActivityExecutionStatusChangedEventArgs> interface. This provides the ability to listen for events raised on an activity. In this case, the activity is interested in listening for events on itself, specifically the Closed event.



public class ForEachActivity : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs> { private IEnumerator enumerator = null; // *** Dependency properties removed for brevity *** protected override ActivityExecutionStatus Execute( ActivityExecutionContext context) { if (context == null) throw new ArgumentNullException("context"); // perform the first "loop" if (this.Iterate(context)) { // if there are no items in the collection, we're done return ActivityExecutionStatus.Executing; } // nothing to do, the activity is done return ActivityExecutionStatus.Closed; } private bool Iterate(ActivityExecutionContext context) { if (this.enumerator == null) this.enumerator = this.Collection.GetEnumerator(); // make sure there is another item and there is a child activity if (!this.enumerator.MoveNext()  this.EnabledActivities.Count != 1) return false; // set the CurrentItem property this.CurrentItem = this.enumerator.Current; ActivityExecutionContextManager aecManager = context.ExecutionContextManager; // create a new context for the child activity ActivityExecutionContext newContext = aecManager.CreateExecutionContext(this.EnabledActivities[0]); // register for the child activity's Closed event newContext.Activity.RegisterForStatusChange(Activity.ClosedEvent, this); // execute the child activity newContext.ExecuteActivity(newContext.Activity); return true; } void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent( object sender, ActivityExecutionStatusChangedEventArgs e) { if (e == null) throw new ArgumentNullException("e"); if (sender == null) throw new ArgumentNullException("sender"); // get the execution context of the child activity ActivityExecutionContext context = sender as ActivityExecutionContext; if (context == null) { throw new ArgumentException( "sender should be an ActivityExecutionContext instance"); } // unsubscribe from the Closed event e.Activity.UnregisterForStatusChange(Activity.ClosedEvent, this); ActivityExecutionContextManager aecManager = context.ExecutionContextManager; // complete the execution context aecManager.CompleteExecutionContext( aecManager.GetExecutionContext(e.Activity)); // do the

next

"loop" if (!this.Iterate(context)) { // no more

loops

, close the for each activity context.CloseActivity(); } } }


The overridden Execute method basically just calls an Iterate helper method that holds the looping logic. In reality, there isn’t a loop to be found anywhere in this code. It is simulated by using the enumerator object, executing a lone child activity, and calling Iterate again after the child activity is closed. When the enumerator.MoveNext method no longer returns true , the ForEachActivity is closed.

In the Iterate method in this example, an ActivityExecutionContextManager instance is used to create a new execution context based on the ForEachActivity ’s child. The new context is then able to execute the child activity using its ExecuteActivity method. This pattern ensures that the child activity executes in its own separate context for every iteration. In the OnEvent method, which captures the Closed event, the execution context is completed with ActivityExecutionContextManager .

Figure 8-1 shows an example workflow that a ForEachActivity method called forEachDate . The first Code activity initializes a List<T> collection, which is bound to the ForEachActivity . The Code activity inside the loop then prints out information about the current item.

image from book
Figure 8-1

The following code represents the code-beside file for the workflow in Figure 8-1:



private void initCollection_ExecuteCode(object sender, EventArgs e) { DateTime startDate = DateTime.Now; this.listOfDates.Add(startDate.AddHours(-5)); this.listOfDates.Add(startDate.AddHours(-4)); this.listOfDates.Add(startDate.AddHours(-3)); this.listOfDates.Add(startDate.AddHours(-2)); this.listOfDates.Add(startDate.AddHours(-1)); } private void printInfo_ExecuteCode(object sender, EventArgs e) { CodeActivity codeActivity = sender as CodeActivity; if (codeActivity != null) { ForEachActivity.ForEachActivity fea = codeActivity.Parent as ForEachActivity.ForEachActivity; if (fea != null) { Console.WriteLine("Current date: " + ((DateTime)fea.CurrentItem).Hour.ToString()); } } }


This workflow uses a collection of DateTime instances just to show that the activity can handle anything that implements IEnumerable . The printInfo event handler obtains a reference to the current item by first using the sender parameter to access the CodeActivity instance and then accessing its parent. The property of ForEachActivity ’s CurrentItem is of type object , so it needs to be cast as a DateTime before its Hour property can be accessed.

Although this is similar to how you develop other activity types, developing an iterative activity has its own set of considerations that must be addressed - the biggest of which is managing activity execution contexts. More specifically, the children of the iterative activity must be executed in their own execution contexts for each iteration so that each execution context can be persisted and treated separately for transactional purposes. The activity execution context is a great feature of Windows Workflow Foundation and really shows its benefits in iterative activities.