This may have been covered elsewhere, but I've been unable to locate a reference to the behavior I'm seeing.
Using WWF Beta 2, I have created a custom composite activity. Inside the composite activity, I have placed a parallel activity with one code activity in each branch (the code activities are trivial - they each write a line to the console).
In the overridden Execute method of the composite activity, I do the following:
1. Check for enabled activities.
2. If enabled activities are found (there is exactly one activity - the parallel activity - available at this point):
A) Create a new AEC by calling:
activityContext.ExecutionContextManager.CreateExecutionContext([child activity]) -- (I haven't seen any concrete examples, but based on the API documentation this seems to have replaced the ActivityExecutionContext.ExecutionContexts.AddNew method in Beta 1).B) Hook the (parallel) child activity's Closed event to a valid handler
C) Tell the execution context created in item (A) to execute the (parallel) activity.
For debugging purposes, I've also added a handler for the parallel activity's Executing event.
When the new composite activity is added to a workflow and the workflow is executed, I can observe the composite activity receiving the focus and beginning execution. I can also see that the child (parallel) activity is started in its own AEC as expected. However, once the parallel activity begins execution (the Executing event fires and trips the handler added for debugging), the workflow becomes unresponsive; neither of the parallel code blocks on the parallel activity's branches is executed, and the Closed handler (obviously) never fires for the child activity, which makes it impossible for the parent (composite) activity to complete.
Note: The parallel activity behaves as expected if run outside the custom composite control.
Any ideas
Thanks,
Jared

Using a Parallel activity in a custom Composite activity (Beta 2)
NewbieElliott
Thanks for your reply, Arjun.
The original project I wrote to test the functionality was heavily edited (I went on to play with something else), so by the time I heard back from you I couldn't refer to the original code. Having read that you didn't experience the same issue I initially saw, I just went back and rewrote the activity and workflow from scratch (code follows).
What I believe to have been the problem involves the Closed event handler hookup and the call to execute the activity (on the child context). As I recall, my original custom activity had syntax for these operations similar to:
Activity childActivity = this.EnabledActivities[_index]; if (childActivity != null){
ActivityExecutionContext childContext = context.ExecutionContextManager.CreateExecutionContext(childActivity);childActivity.Closed += OnClosed;
childContext.ExecuteActivity(childActivity);
}
Using the above syntax, I am able to make my new activity fail in the same manner it failed initially. Changing the syntax to:
Activity childActivity = this.EnabledActivities[_index]; if (childActivity != null){
ActivityExecutionContext childContext = context.ExecutionContextManager.CreateExecutionContext(childActivity);childContext.Activity.Closed += OnClosed;
childContext.ExecuteActivity(childContext.Activity);
}
The difference is that instead of referring directly to the Activity about to execute (childActivity in this case), the code must access the Activity as a property of the ActivityExecutionContext. While I understand some possible causes for this behavior, it's somewhat non-obvious. It implies to me that the child context is operating on a copy of (or at least an indirect reference to) the child activity, and does not expose a reference to the actual instance. Perhaps I've missed something in the documentation about this - please let me know if I have.
Here's the complete code, and thanks again...
CustomParallelActivity.cs
using
System;using
System.ComponentModel;using
System.ComponentModel.Design;using
System.Collections;using
System.Drawing;using
System.Workflow.ComponentModel;using
System.Workflow.ComponentModel.Design;using
System.Workflow.ComponentModel.Compiler;using
System.Workflow.ComponentModel.Serialization;using
System.Workflow.Runtime;using
System.Workflow.Activities;using
System.Workflow.Activities.Rules;namespace
ActivityTest{
[
Designer(typeof(SequentialActivityDesigner))] public partial class CustomParallelActivity: CompositeActivity{
private int _index = 0; public CustomParallelActivity(){
InitializeComponent();
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext){
StartActivityAtIndex(_index, executionContext);
return ActivityExecutionStatus.Executing;}
private void StartActivityAtIndex(int activityIndex, ActivityExecutionContext context){
if (this.EnabledActivities.Count > _index){
if (context != null){
Activity childActivity = this.EnabledActivities[_index]; if (childActivity != null){
ActivityExecutionContext childContext = context.ExecutionContextManager.CreateExecutionContext(childActivity);childContext.Activity.Closed += OnClosed;
childContext.ExecuteActivity(childContext.Activity);
Console.WriteLine("Requested execution for activity '{0}'", childContext.Activity.Name);}
}
}
else{
context.CloseActivity();
}
}
void OnClosed(object sender, ActivityExecutionStatusChangedEventArgs e){
// Cast the sender as an execution context ActivityExecutionContext context = sender as ActivityExecutionContext; if (context != null){
// Get the closed activity's context and unload it Activity childActivity = e.Activity; if (childActivity != null){
ActivityExecutionContext childContext = context.ExecutionContextManager.GetExecutionContext(childActivity); if (childContext != null){
childContext.Activity.Closed -= OnClosed;
context.ExecutionContextManager.CompleteExecutionContext(childContext);
}
// Move the index ahead to the next enabled activity in sequence._index++;
// Attempt to start the next activityStartActivityAtIndex(_index, context);
}
else{
throw new ArgumentException("Cannot proceed. Current context has a null Activity.");}
}
else{
throw new ArgumentException("Argument must be castable to type ActivityExecutionContext.", "sender");}
}
private void codeActivity3_ExecuteCode(object sender, EventArgs e){
Console.WriteLine("The parallel activity is about to execute...");}
private void codeActivity1_ExecuteCode(object sender, EventArgs e){
Console.WriteLine("branch #1 of the parallel activity is executing...");}
private void codeActivity2_ExecuteCode(object sender, EventArgs e){
Console.WriteLine("branch #2 of the parallel activity is executing...");}
}
}
CustomParallelActivity.Designer.csusing
System;using
System.ComponentModel;using
System.ComponentModel.Design;using
System.Collections;using
System.Drawing;using
System.Reflection;using
System.Workflow.ComponentModel;using
System.Workflow.ComponentModel.Design;using
System.Workflow.ComponentModel.Compiler;using
System.Workflow.ComponentModel.Serialization;using
System.Workflow.Runtime;using
System.Workflow.Activities;using
System.Workflow.Activities.Rules;namespace
ActivityTests{
public partial class CustomParallelActivity{
#region
Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent(){
this.CanModifyActivities = true; this.parallelActivity1 = new System.Workflow.Activities.ParallelActivity(); this.sequenceActivity1 = new System.Workflow.Activities.SequenceActivity(); this.sequenceActivity2 = new System.Workflow.Activities.SequenceActivity(); this.codeActivity1 = new System.Workflow.Activities.CodeActivity(); this.codeActivity2 = new System.Workflow.Activities.CodeActivity(); this.codeActivity3 = new System.Workflow.Activities.CodeActivity(); // // parallelActivity1 // this.parallelActivity1.Activities.Add(this.sequenceActivity1); this.parallelActivity1.Activities.Add(this.sequenceActivity2); this.parallelActivity1.Name = "parallelActivity1"; // // sequenceActivity1 // this.sequenceActivity1.Activities.Add(this.codeActivity1); this.sequenceActivity1.Name = "sequenceActivity1"; // // sequenceActivity2 // this.sequenceActivity2.Activities.Add(this.codeActivity2); this.sequenceActivity2.Name = "sequenceActivity2"; // // codeActivity1 // this.codeActivity1.Name = "codeActivity1"; this.codeActivity1.ExecuteCode += new System.EventHandler(this.codeActivity1_ExecuteCode); // // codeActivity2 // this.codeActivity2.Name = "codeActivity2"; this.codeActivity2.ExecuteCode += new System.EventHandler(this.codeActivity2_ExecuteCode); // // codeActivity3 // this.codeActivity3.Name = "codeActivity3"; this.codeActivity3.ExecuteCode += new System.EventHandler(this.codeActivity3_ExecuteCode); // // CustomParallelActivity // this.Activities.Add(this.codeActivity3); this.Activities.Add(this.parallelActivity1); this.Name = "CustomParallelActivity"; this.CanModifyActivities = false;}
#endregion
private ParallelActivity parallelActivity1; private SequenceActivity sequenceActivity1; private CodeActivity codeActivity1; private SequenceActivity sequenceActivity2; private CodeActivity codeActivity2; private CodeActivity codeActivity3;}
}
Fekih Mehdi
There was nothing really to resolve. Mainly I was pointing out that some of the BETA 2 features resulted in an API behavior that was unintuitive to users. I haven't changed my mind about it, but I'm not losing any sleep over it, either - particularly since this was, like, a year or so ago...
Are you just in the process of touching all these old, unresolved issues to see if anyone's still tracking them or something If so, feel free to close the thread. Whole civilizations have risen and crumbled since I wrote that post; I've even interviewed with your team in the intervening months.
Thanks for checking, anyway.
Jared
prasad knv
You're more dedicated than I am, Angel.
{slaps self on forehead}
That must be why I didn't get the job - I distinctly remember scoffing when asked if I would be willing to cull through presumably dead forum threads looking for unresolved issues as much as a year after they were last touched.
Thanks for checking; go have a weekend!
Jared
Ken Harris
Hi,
Hopefully you've reached a resolution. If for some reason you have not, please make another post.
Thanks!
Angel
GraemeE
Jared,
I tried to repro this scenario and things seem to work fine for me. I've pasted my code below - maybe you could compare it to your version I'd be interested in knowing the difference and if there are any potential bugs.
Activity1.cs
using
System;using
System.ComponentModel;using
System.ComponentModel.Design;using
System.Collections;using
System.Drawing;using
System.Workflow.ComponentModel;using
System.Workflow.ComponentModel.Design;using
System.Workflow.ComponentModel.Compiler;using
System.Workflow.ComponentModel.Serialization;using
System.Workflow.Runtime;using
System.Workflow.Activities;using
System.Workflow.Activities.Rules;//NOTE: When changing the namespace; please update XmlnsDefinitionAttribute in AssemblyInfo.cs
namespace
WorkflowConsoleApplication5{
public partial class Activity1 : System.Workflow.ComponentModel.CompositeActivity{
public Activity1(){
InitializeComponent();
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext){
ActivityExecutionContext ex = executionContext.ExecutionContextManager.CreateExecutionContext(this.EnabledActivities[0]);ex.ExecuteActivity(ex.Activity);
ex.Activity.Closed +=
new EventHandler<ActivityExecutionStatusChangedEventArgs>(Activity_Closed); return base.Execute(executionContext);}
void Activity_Closed(object sender, ActivityExecutionStatusChangedEventArgs e){
ActivityExecutionContext context = sender as ActivityExecutionContext; //custom close logic here.}
private void codeActivity1_ExecuteCode(object sender, EventArgs e){
Console.WriteLine("foo");}
}
}
Activity1.Designer.cs
using
System;using
System.ComponentModel;using
System.ComponentModel.Design;using
System.Collections;using
System.Drawing;using
System.Reflection;using
System.Workflow.ComponentModel;using
System.Workflow.ComponentModel.Design;using
System.Workflow.ComponentModel.Compiler;using
System.Workflow.ComponentModel.Serialization;using
System.Workflow.Runtime;using
System.Workflow.Activities;using
System.Workflow.Activities.Rules;namespace
WorkflowConsoleApplication5{
public partial class Activity1{
#region
Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent(){
this.CanModifyActivities = true; this.parallelActivity1 = new System.Workflow.Activities.ParallelActivity(); this.sequenceActivity3 = new System.Workflow.Activities.SequenceActivity(); this.sequenceActivity4 = new System.Workflow.Activities.SequenceActivity(); this.codeActivity1 = new System.Workflow.Activities.CodeActivity(); this.codeActivity3 = new System.Workflow.Activities.CodeActivity(); // // parallelActivity1 // this.parallelActivity1.Activities.Add(this.sequenceActivity3); this.parallelActivity1.Activities.Add(this.sequenceActivity4); this.parallelActivity1.Name = "parallelActivity1"; // // sequenceActivity3 // this.sequenceActivity3.Activities.Add(this.codeActivity1); this.sequenceActivity3.Name = "sequenceActivity3"; // // sequenceActivity4 // this.sequenceActivity4.Activities.Add(this.codeActivity3); this.sequenceActivity4.Name = "sequenceActivity4"; // // codeActivity1 // this.codeActivity1.Name = "codeActivity1"; this.codeActivity1.ExecuteCode += new System.EventHandler(this.codeActivity1_ExecuteCode); // // codeActivity3 // this.codeActivity3.Name = "codeActivity3"; this.codeActivity3.ExecuteCode += new System.EventHandler(this.codeActivity1_ExecuteCode); // // Activity1 // this.Activities.Add(this.parallelActivity1); this.Name = "Activity1"; this.CanModifyActivities = false;}
#endregion
private SequenceActivity sequenceActivity1; private SequenceActivity sequenceActivity2; private CodeActivity codeActivity2; private ParallelActivity parallelActivity1; private CodeActivity codeActivity1; private SequenceActivity sequenceActivity3; private SequenceActivity sequenceActivity4; private CodeActivity codeActivity3;}
}
DavidAWinter
Haha.
Yep, I'm going through to make sure no soldiers were left behind.
Thanks Jared.