Jump to content
Jud

Strange problem skipping loop with tTask

Recommended Posts

I've had a perplexing problem for about 2 days. Basically if a tTask is inside a loop, the loop isn't executed. I extracted a section of code from my program, and the short sample code below has the problem.  After the SetLength line is executed, it goes to the END of the procedure.  

This used to work.  If I take the line TaskArray[ counter] := - the loop is executed. If I take out the loop and replace it by counter := 0, it works.

Am I missing something?  What is going wrong?


procedure TForm2.RunButtonClick(Sender: TObject);

const NumberOfParts = 1;

var counter : integer;
    TaskArray : array of iTask;

begin { --- run button --- }
SetLength( TaskArray, NumberOfParts);

for counter := 0 to NumberOfParts - 1 do
begin
  try
    TaskArray[ counter] := tTask.run( procedure
      begin
        RunWork( counter);
      end);
  except on E:EAggregateException do ShowMessage( E.ToString);
  end; // try
end; // for counter
end; { --- run button --- }

 

Share this post


Link to post

Can confirm with Delphi Alexandria 11.1:

 

The program is executed correctly, but the stepping through it with F8 inside the debugger jumps around:

 

It goes like this:

  1. Begin
  2. SetLength
  3. End of method
  4. Back to for loop
  5. End of method
  6. back to the foor loop
  7. end of method
  8. done

I suppose it's some weird kind of compiler optimization or the debugger just setting the breakpoints in the wrong places.

Share this post


Link to post

The problem in your code is that the anonymous method calling RunWork uses the counter variable directly. While it is correct that this variable is captured for the anonymous method, it is captured by its address and not by its value. Therefore the RunWork call uses the value of counter that is given in the moment of that call. Usually that is different from what you expect.

 

You can solve this by injecting a method returning the anonymous method with the counter value at that time.

function MakeRunWork(ACounter: Integer): TProc;
begin
  Result :=
    procedure
    begin
      RunWork(ACounter);
    end;
end;

procedure TForm780.Button1Click(Sender: TObject);
const
  NumberOfParts = 1;
var
  counter: integer;
  TaskArray: array of iTask;
begin { --- run button --- }
  SetLength(TaskArray, NumberOfParts);
  for counter := 0 to NumberOfParts - 1 do
  begin
    try
      TaskArray[counter] := TTask.Run(MakeRunWork(counter));
    except
      on E:EAggregateException do
        ShowMessage( E.ToString);
    end; // try
  end; // for counter
end;

 

Edited by Uwe Raabe
  • Like 5
  • Thanks 1

Share this post


Link to post
11 minutes ago, Lars Fosdal said:

Captures problems can be easy to miss.

If you have a loop you will have a capture problem, except in most trivial cases. It is not that hard to spot. The main problem is that plenty of people are completely unaware of the issue as they haven't used anonymous procedures much.

  • Like 1

Share this post


Link to post
2 minutes ago, Dalija Prasnikar said:

plenty of people are completely unaware of the issue

Precisely. 

Share this post


Link to post
15 hours ago, Jud said:

I've had a perplexing problem for about 2 days. Basically if a tTask is inside a loop, the loop isn't executed. I extracted a section of code from my program, and the short sample code below has the problem.  After the SetLength line is executed, it goes to the END of the procedure.  

 

==================

More information:

 

1. Sometimes the demo works. 

 

2. But when that isn't working, the program skips the loop (doesn't even go to the FOR statement) and goes to the END of the procedure.  Then it locks up big time.  After a few seconds, it gives a stack overflow message, and it points to various places in System units (there doesn't seem to be a pattern).  It locks up so bad that I can't Close Window or end it with the task manager.  I have to restart the computer.

 

3. I remembered that you can't call a procedure that depends on the value of the loop counter that way.  The only way I know to do it is to call a procedure from within the loop and that procedure sets up task and runs it.  So I'm going to make that change and I expect it will fix the problem.

 

Share this post


Link to post

Wouldn't TParallel.For() be more appropriate in this situation?

procedure TForm2.RunButtonClick(Sender: TObject);
const
  NumberOfParts = 1;
begin
  TParallel.For(0, NumberOfParts - 1,
    procedure(counter: Integer)
    begin
      RunWork(counter);
    end
  );
  // or simply:
  // TParallel.For(0, NumberOfParts - 1, RunWork);
end;

 

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×