Jud 1 Posted August 10, 2023 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
Der schöne Günther 316 Posted August 10, 2023 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: Begin SetLength End of method Back to for loop End of method back to the foor loop end of method 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
Uwe Raabe 2057 Posted August 10, 2023 (edited) 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 August 10, 2023 by Uwe Raabe 5 1 Share this post Link to post
Lars Fosdal 1792 Posted August 10, 2023 Captures problems can be easy to miss. Share this post Link to post
Dalija Prasnikar 1396 Posted August 10, 2023 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. 1 Share this post Link to post
Lars Fosdal 1792 Posted August 10, 2023 2 minutes ago, Dalija Prasnikar said: plenty of people are completely unaware of the issue Precisely. Share this post Link to post
Jud 1 Posted August 10, 2023 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
Remy Lebeau 1398 Posted August 10, 2023 (edited) 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 August 10, 2023 by Remy Lebeau 1 Share this post Link to post
Patrick PREMARTIN 74 Posted August 11, 2023 And to not block the program during the loop, the Parallel.For can be put in a thread. 🙂 Share this post Link to post