|
The Activity Execution ContextThe 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
The
ActivityExecutionContext
class also exposes
|
|
Developing an Iterative Activity
To
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
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
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.
The following code represents the
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
|