Hello,
I have some strange behavior with a CAG shape that I would appreciate an expert's opinion on. It is not working the way I expected, but I need to understand why.
Ok, to start at the beginning, I am using the June CTP release of WF and VS.NET 2005. I have a sequential workflow that consists of a CAG shape. The CAG contains 4 child shapes:
- delay1
- code1
- delay2
- code2
On the CAG I have an UntilCondition that is a CodeCondition and it points to the following method:
void untilCondition(object sender, ConditionalEventArgs e)
{
if (counter == maxNumber)
{
e.Result = true;
}
else
{
counter++;
e.Result = false;
}
}
The logic is not meant to make sense, its just a loop that will run a number of times then terminate. The local variables, "counter" and "maxNumber" are set to 0 and 4, respectively. So what I would expect is for the child activities in the CAG to execute 4 times each as a repeat-until loop would do. Ultimately, what I intend to implement here is a data-driven condition so the number of iterations will not be fixed for each workflowInstance, but that's beside the point.
For each of the 4 child activities in the CAG I have a WhenCondition that is also a CodeCondition. Each CodeCondition points to the following method:
void whenCondition(object sender, ConditionalEventArgs e)
{
e.Result = (counter != maxNumber);
}
The intention here is for each child activity to execute while the CAG until condition has not been met. If I omit this WhenCondition on each child activiy, the children only execute once regardless of the number of times the CAG UntilCondition is evaluated. So i figured I had to explicitly include a WhenCondition that is essentially the opposite of the UntilCondition in order to get each child executing on each iteration. I understand this is what makes the CAG quite powerful in theory - a looping switch statement is the best description I've heard for this control. In practice however my understanding of how it works is lacking.
When I have the workflow in deug mode with breakpoints on each of the condition methods listed above (and the two code methods), I find that the following occurs:
1. The CAG UntilCondition evaluates to false (counter is now 1)
2. The WhenCondition evaluates to true for the delay1 activity
3. The WhenCondition evaluates to true for the code1 activity
4. The WhenCondition evaluates to true for the dealy2 activity
5. The WhenCondition evaluates to true for the code2 activity
6. The code1 code executes
7. The code2 code executes
8. The CAG UntilCondition evaluates to false (counter is now 2)
So far so good. Now something wierd happens:
9. The WhenCondition evaluates to true for the code1 activity (for some reason code1 is being evaluated now, not delay1 which was evaluated first in the initial iteration).
10. The CAG UntilCondition evaluates to false (counter is now 3)
11. Then WhenCondition evaluates to true for the code2 activity (code2 being the 4th child)
12. The CAG UntilCondition evaluates to false (counter is now 4)
13. The WhenCondition evaluates to false for the delay1 activity (this is now the end condition of the CAG)
14. The code1 code executes.
15. The CAG UntilCondition evaluates to true and the processing stops.
Clearly, the steps 9-15 are quite strange and seemingly random. The WhenConditions of the child activities does not follow the order that they were evaluated on the first iteration in steps 1-8. Also the UntilCondition is being evaluated after each WhenCondition, whereas the first iteration it was evaluated only after each and every child WhenCondition was evaluated. And lastly, even though the code2 WhenCondition was evaluated to true, the code2 code did not run - only code1 did.
I am totally lost in understanding how and why I am witnessing this behavior. Can anyone shed some light on this
I also tried another scenario where I replaced the delay activities with code activities. In this scenario, the first iteration followed the same pattern as above, however on the second iteration the WhenConditions of the child activities evaluated in the correct order. However, despite 2 of the child code activity WhenConditions evaluating to true, none of the code activities actually ran on the second iteration (unlike the original scenario when code1 ran on the second iteration).
So to recap, what I expect to happen is for each child activitiy in the CAG to execute 4 times regardless of the number of child activities and whether or not delay nodes are included. I exepct each code to execute if the WhenCondition evaluates to true and I expect the UntilCondition to be evaluated only once per iteration. Can anyone provide an explanation for what is happening here and, hopefully, a solution to my problem
Thanks in advance
Steve

Unexpected behavior in CAG - is this a bug or a misunderstood feature?
Bobby110uk
The reason for the strange behavior is that when each CAG child completes execution the When condition for all non-running children are evaluated. When codeActivity1 completes codeActivity4 isn't executing so codeActivity1 and codeActivity4 are evaluated. Next codeActivity2 completes so 1, 2 & 4 are evaluated and finally when 3 completes they are all evaluated. With a delay activity if it is still blocking because the TimeoutDuration has not expired its When condition will not be evaluated, only non-executing activity are evaluted.
bkejser
Hi Bryant,
Thanks very much for taking the time to investigate. Your tip about getting rid of the UntilCondition was interesting, however I am still not satisified I understand what is going on. It is still difficult to ensure that all children execute a set number of times because the counter variable needs to be adjusted depending on the number of children activities. Maybe this just seems odd because it is a canned example - when I use a realistic data-driven condition this may make more sense.
However, there is still some strange behavior here. As your code output indicates, the WhenConditions continue to fire even after all children have evaluated false. And, as your code output also shows, the order which child actvities are evaluated during this sequence is seeminlgy random. I got the same strange behavior when I used a different number of child activities and a set maxNumber value of 4. With 2 code and 2 delay children, the counter went up to 14 before the processing finally finished and with 3 code activities the counter went up to 10. The code's executed the correct number of times in each case, but the fact that they continue to be evaluated after all return false is against when is being advertised for this control.
I fear something buggy is going with the CAG. I have veyr little confidence it in. Can anyone alieviate my fears and explain to me what is going on
Thanks in advance
Steve
Xythe
Thanks to Tom and Serge for clarifying.
My understanding of this control is a little better now.
Steve
Deco
To illustrate Tom' s answer, I've tested and commented the last sample (with 3 code Activities, without the until condition, without the delays, with WhenCondition) with maxNumber = 6.
Console displays are in red , almost each line is commented in [my comment]
Calling when on codeActivity1 with counter at 1 of 6
[my comment: codeActivity1 is not executing ->whencondition evaluated, returns true->will execute]
Calling when on codeActivity2 with counter at 2 of 6
[my comment: codeActivity2 is not executing ->whencondition evaluated, returns true->will execute]
Calling when on codeActivity3 with counter at 3 of 6
[my comment: codeActivity3 is not executing ->whencondition evaluated, returns true->will execute]
codeActivity1 Executing...
codeActivity2 Executing...
codeActivity3 Executing...
Calling when on codeActivity1 with counter at 4 of 6
[my comment: codeActivity1 is not executing ->whencondition evaluated, returns true->will execute]
Calling when on codeActivity2 with counter at 5 of 6
[my comment: codeActivity2 is not executing ->whencondition evaluated, returns true->will execute]
Calling when on codeActivity3 with counter at 6 of 6
[my comment: codeActivity3 is not executing ->whencondition evaluated, returns false->activity3 won't execute anymore]
codeActivity1 Executing...
codeActivity2 Executing...
Calling when on codeActivity1 with counter at 7 of 6
[my comment: codeActivity1 is not executing ->whencondition evaluated, returns false-> activity1 won't execute anymore]
Calling when on codeActivity3 with counter at 8 of 6
[my comment: codeActivity2 is probably still running->whencondition not evaluated
codeActivity3 is not executing->condition evaluated]
Calling when on codeActivity1 with counter at 9 of 6
[my comment: codeActivity1 is not executing->whencondition evaluated, returns false-> activity1 won't execute anymore]
Calling when on codeActivity2 with counter at 10 of 6
[my comment: codeActivity2 is not executing->whencondition evaluated, returns false-> activity2 won't executed anymore]
Calling when on codeActivity3 with counter at 11 of 6
[my comment: codeActivity3 is not executing->whencondition evaluated, returns false-> activity3 won't execute anymore]
[my comment: all activities conditions returned false=> the Cag exit]
So the Cag runs until it is in a "stable" situation : all conditions return false
a condition can be false at moment t1 but true at t2, so the activity could rerun.
Don't hesitate to comment my comments
Serge
DrLectEr
From OdeToCode:
So you would need to remove your UntilCondition and move your incrementing logic to the WhenCondition. I tested this out with four code activities and it seemed to work as advertised:
public sealed partial class Workflow1: SequentialWorkflowActivity
{
int counter = 0;
const int MaxNumber = 16;
public Workflow1()
{
InitializeComponent();
}
void whenCondition(object sender, ConditionalEventArgs e)
{
CodeActivity ca = sender as CodeActivity;
counter++;
Console.WriteLine("Calling when on {0} with counter at {1} of {2}", ca.Name, counter, MaxNumber);
e.Result = (counter < MaxNumber);
}
private void codeActivity_ExecuteCode(object sender, EventArgs e)
{
CodeActivity ca = sender as CodeActivity;
Console.WriteLine("{0} Executing...", ca.Name);
}
}
When I execute this wf I get the following output:
Calling when on codeActivity1 with counter at 1 of 16
Calling when on codeActivity2 with counter at 2 of 16
Calling when on codeActivity3 with counter at 3 of 16
Calling when on codeActivity4 with counter at 4 of 16
codeActivity1 Executing...
codeActivity2 Executing...
codeActivity3 Executing...
codeActivity4 Executing...
Calling when on codeActivity1 with counter at 5 of 16
Calling when on codeActivity2 with counter at 6 of 16
Calling when on codeActivity3 with counter at 7 of 16
Calling when on codeActivity4 with counter at 8 of 16
codeActivity1 Executing...
codeActivity2 Executing...
codeActivity3 Executing...
codeActivity4 Executing...
Calling when on codeActivity1 with counter at 9 of 16
Calling when on codeActivity2 with counter at 10 of 16
Calling when on codeActivity3 with counter at 11 of 16
Calling when on codeActivity4 with counter at 12 of 16
codeActivity1 Executing...
codeActivity2 Executing...
codeActivity3 Executing...
codeActivity4 Executing...
Calling when on codeActivity1 with counter at 13 of 16
Calling when on codeActivity2 with counter at 14 of 16
Calling when on codeActivity3 with counter at 15 of 16
Calling when on codeActivity4 with counter at 16 of 16
codeActivity1 Executing...
codeActivity2 Executing...
codeActivity3 Executing...
Calling when on codeActivity1 with counter at 17 of 16
Calling when on codeActivity4 with counter at 18 of 16
Calling when on codeActivity1 with counter at 19 of 16
Calling when on codeActivity2 with counter at 20 of 16
Calling when on codeActivity4 with counter at 21 of 16
Calling when on codeActivity1 with counter at 22 of 16
Calling when on codeActivity2 with counter at 23 of 16
Calling when on codeActivity3 with counter at 24 of 16
Calling when on codeActivity4 with counter at 25 of 16
--
Bryant