1

Hopefully a simple one. I am using an OTL Parallel.For loop to process lots of data. The amount of data can change and if there is a lot (that takes over 2 seconds) Windows flickers the application form and gives a temporary "not responding" status in the title bar.

To get around this I thought I could put the procedure with the Parallel.For loop inside an OTL Async call, like

done:=false;
Async(ProcedureThatDoesParallelFor).Await(
procedure begin
done:=true;
end);
repeat application.processmessages until done=true;

This works (or seems to work) but can lead to the program just aborting/exiting without any error messages. It only seems to cause the silent abort problem when the Parallel.For loop is very quick to run.

If I remark the above code and take the call to ProcedureThatDoesParallelFor outside of it the app runs fine without unexpected quitting, so I am assuming it must be the Async call causing the problem. Or a combination of Parallel.For within Async?

Is using Async the best way to run another procedure and wait for it to finish? Is there a better OTL way of doing this?

Thanks for any ideas or solutions.

Here is the simplest example to show the crashing error. Single form with a memo and button. Click the button and the program will hang around iteration 300.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,OtlParallel,OtlTaskControl;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure AsyncParallelFor;
var iterations:integer;
    blah:integer;
begin
     iterations:=10;
     //for iter:=0 to limit-1 do
     Parallel.For(0,iterations-1).Execute(procedure(iter:integer)
     var x,y:integer;
     begin
          for y:=0 to 50 do
          begin
               for x:=0 to 50 do
               begin
                    blah:=x+y;
               end;
          end;
     end);
end;

procedure AsyncProcedure;
var done:boolean;
begin
     done:=false;
     Parallel.Async(
          procedure
          begin
               //executed in background thread
               AsyncParallelFor;
          end,
          Parallel.TaskConfig.OnTerminated(
          procedure (const task: IOmniTaskControl)
          begin
               //executed in main thread after the async has finished
               done:=true;
          end
          )
     );
     //this point is reached immediately after the call to Async
     //the repeat loop waits until the Async is finished being signalled via done variable
     repeat
           application.processmessages;
     until done=true;
end;


procedure TForm1.Button1Click(Sender: TObject);
var iters:integer;
begin
     iters:=0;
     repeat
           memo1.lines.add('Iteration '+inttostr(iters)+'...');
           memo1.lines.add('Before Async');
           application.processmessages;
           AsyncProcedure;
           memo1.lines.add('After Async');
           application.processmessages;
           inc(iters);
     until 1>2;


end;

end.

AsyncParallelFor shows the basic nested loops. Just a simple addition in there to demo the issue.

AsyncProcedure does the OTL Async call and waits for the return.

I have a lot of non parallel code before and after the call to AsyncProcedure that need to wait for the parallel.for loop to finish.

If I change the button click to call AsynParallelFor directly without the Async then there is no hang.

Some1Else
  • 715
  • 11
  • 26
  • It looks you fire an async method, and then await it immediately. This kinda defeats the purpose, since this code will block the calling (=main?) thread until ProcedureThatDoesParallelFor is done (the app is frozen). After that, your repeat loop is entered, but by then it's already done, and `done` will be true already. The only thing that loop does is forcibly call Application.ProcessMessages once (because it's a repeat loop, not a while loop). If you need Application.ProcessMessages, you're _probably_ doing it wrong anyway, but mind that in this scenario you're not even using it effectively. – GolezTrol Mar 11 '19 at 08:45
  • But I can't see your entire code, especially the context in which you call this, so why do you need to wait. Wat for? If you want to somehow to get the output of the parallel for loop, you might want to have a look at the example [Parallel for with synchronized output](http://www.omnithreadlibrary.com/book/chap10.html#howto-parallelForSyncOut) from the OTL website. – GolezTrol Mar 11 '19 at 08:46
  • What I need is a way to call ProcedureThatDoesParallelFor from the main thread. During the time the procedure runs I do not need to update the main thread with any statistics. I need to be able to get the main thread to wait for the procedure to finish without it hanging and getting Windows to show "not responding". I needs to wait because the main thread processes the data the parallel.for loop processes, so it needs to know the processing is finished before continuing on. What is the best way to do this with OTL? – Some1Else Mar 11 '19 at 09:19
  • I think the thought there is incorrect. It seems you don't want the main thread to _wait_, but to let it continue to run, and only _respond_, do something specific, when the async procedure is done. If you really want it to wait, you shouldn't call `Application.ProcessMessages`. And if you want it to continue, you shouldn't call `Await`.. What you actually want, is a way to let OTL run a little piece of "after" code, which runs in the main thread, but is executed after the async stuff is done. From that code, you can update your UI with the results... – GolezTrol Mar 11 '19 at 14:54
  • Fortunately, it seems OTL has exactly that. `Async` actually accepts two procs. The first is called in the background thread. The second one is called in the main thread. See this example: https://www.thedelphigeek.com/2011/04/simple-background-tasks-with.html I hope that example is still relevant, since it's from 2011, but I must admit I haven't used OTL recently (and not much at all), so I don't know if it has changed much. (It's probably okay, since it is [linked from the otl.com](http://www.omnithreadlibrary.com/documentation/#articles)) – GolezTrol Mar 11 '19 at 14:55
  • No help there unfortunately. Maybe I can approach from inside the Parallel.For loop? What would be the easiest way to get the main thread/form to "application.processmessages" during the Parallel.For loop? If I just put in an application.processmessages call it does stop the program quitting without wanring (so that is one help) but if the Parallel.For loop is lengthy the main window still gets the flicker and "not responding" from Windows. I just want a way to let Windows know that the program has not hung during a lengthy parallel.for loop. – Some1Else Mar 12 '19 at 01:44
  • Why no help there? It contains an actual concrete example for calling code after some parallel action for update the UI (in the example to enable the button again after the async action is done. Why doesn't it work for you? – GolezTrol Mar 12 '19 at 10:53
  • 1
    Btw, do get rid of Application.ProcessMessages. You're already running stuff asynchronously, and that should be enough. Everything that is running async is not blocking the main thread. See the couple of nice answers to [what Application.ProcessMessages in Delphi is doing](https://stackoverflow.com/questions/25181713/i-do-not-understand-what-application-processmessages-in-delphi-is-doing) for its pitfalls. The only problem you have is that you `Await` the call, while you shouldn't. – GolezTrol Mar 12 '19 at 11:57
  • That async solution (updated/correct code is here https://www.thedelphigeek.com/2011/07/life-after-21-async-redux.html) still gives me the same issue of getting a "not responding" issue if the parallel.for loop takes a long time. I got rid of the async as it was the wrong logic and doesn't help, but I need a way to let the main form respond to messages during the parallel.for loop. – Some1Else Mar 12 '19 at 16:59
  • Could you show the code that you tried that does use Async without Await and still doesn't work? It should work, so maybe we can find an error in it? Otherwise, it's just my advice, followed by your "doesn't work", and we could go on like this forever. You can edit the question to add relevant details with proper formatting. – GolezTrol Mar 12 '19 at 19:43
  • In `AsyncProcedure`, get rid of the `done` variable and in `OnTerminated` event, just update the memo. And the loop at the end is horrible. Never poll in an environment that is event driven. Just remove all that. Finally in the `Button1Click` event, just update the memo and call `AsyncProcedure`, nothing else. The loop here is as horrible as mentioned above. Never ever try this stunt again. – LU RD Mar 13 '19 at 07:20

1 Answers1

1

In your AsyncProcedure, there is no need to repeatedly wait for the async call to finish. This defeats the event driven model that the OS is built on. Specially calling Application.ProcessMessages can lead to unexpected things to happen.

Use the OnTerminate event to signal that the async call is done and there take actions what to do next. In the example provided in this answer, a callback method is used to handle that.

A button click method is supposed to do only a short task, not an eternal loop with the dreaded calls to Application.ProcessMessages.

Instead, use a flag to indicate whether a new call to the async procedure should be done.


Below is an example how to modify your test with a callback method and an event driven model (I did not try the OTL calls, but I would be surprised if the library is the cause of your problems):

type
  TForm1 = class(TForm)
    BtnStart: TButton;
    BtnStop: TButton;
    Memo1: TMemo;
    procedure BtnStartClick(Sender: TObject);
    procedure BtnStopClick(Sender: TObject);
  private
  { Private declarations }
    fDoRepeat : Boolean;
    fIterations : Integer;
    procedure MyCallbackMethod(Sender : TObject);
  public
  { Public declarations }
  end;

procedure AsyncProcedure( MyCallbackMethod : TNotifyEvent);
begin
  Parallel.Async(
    procedure
    begin
      //executed in background thread
      AsyncParallelFor;
    end,
  Parallel.TaskConfig.OnTerminated(
    procedure (const task: IOmniTaskControl)
    begin
      //executed in main thread after the async has finished
      MyCallbackMethod(Nil);
    end)
  );
end;

procedure TForm1.MyCallbackMethod(Sender : TObject);
begin
  if (Sender = nil) then // Callback from AsyncProcedure
     memo1.lines.add('After Async');
  if fDoRepeat then begin
    Inc(fIterations);
    memo1.lines.add('Iteration '+inttostr(fIterations)+'...');
    memo1.lines.add('Before Async');
    AsyncProcedure(MyCallbackMethod);        
  end;
end;

procedure TForm1.BtnStartClick(Sender: TObject);
begin
  fDoRepeat := true;
  fIterations := 0;
  BtnStart.Enabled := false;
  MyCallbackMethod(Sender);  // Start iteration event looping
end;

procedure TForm1.BtnStopClick(Sender: TObject);
begin
  fDoRepeat := false;  // Stop iteration loop
  BtnStart.Enabled := true;
end;

Update

Running the above test in debug mode gave:

Out of memory

after 387 iterations in an OTL unit allocating memory for a buffer (and it is running slow).

Testing the OTL Parallel.For() with some other examples from Updating a Progress Bar From a Parallel For Loop (Plus Two Bonuses) did not improve the outcome. Program hangs at 400 iterations.


Using the bug ridden Delphi PPL did in fact work, though.

Uses
  Threading;

procedure AsyncParallelFor;
var
  iterations:integer;
  blah:integer;
begin
  iterations := 10;
  TParallel.For(0,iterations-1,
    procedure(iter : integer)
    var x,y:integer;
    begin
      for y := 0 to 50 do
      begin
        for x := 0 to 50 do
        begin
          blah := x+y;
        end;
      end;
    end);
end;

procedure AsyncProcedure( MyCallbackMethod : TNotifyEvent);
begin
  TTask.Run(
    procedure
    begin
      AsyncParallelFor;
      //executed in main thread after the async has finished
      TThread.Queue(nil,
        procedure
        begin
          MyCallbackMethod(Nil);
        end
      );
  end);
end;

To update the GUI within a parallel for loop, just use this code within the loop:

TThread.Queue(nil,
  procedure 
  begin
    // Some code that updates the GUI or calls a method to do so.
  end
);
LU RD
  • 34,438
  • 5
  • 88
  • 296
  • Thanks for that. I have so much other code before and after the parallel.for I will need to try and get working with this method. Is there any way to allow the main thread to respond to system messages during a parallel.for loop? If I could stop the "not responding" issue during long parallel.for loops I wouldn't even need all the Async and callback method etc. – Some1Else Mar 13 '19 at 20:34
  • @Some1Else, see my updated answer. Something in the OTL is not correct, or the use case here is not covered. Using Delphi PPL seems ok though. Putting large calculations or other types of heavy computations into one or several threads is a step forward, once the skills can be mastered. To update the GUI from a thread async, use `TThread.Queue()` as mentioned in my answer. – LU RD Mar 13 '19 at 21:18
  • @gabr, (author of OTL) can you spot what is wrong with this OTL Parallel.Async/Parallel.For example? I used version 3.07.7 and Delphi 10.3.1. Using Parallel.For only with .NoWait and OnTerminate option failed as well. – LU RD Mar 13 '19 at 21:29
  • That confirms my crash finding. After some hassles trying to get the Async method working, I broke up my nested parallel.for outer loops into batches of 100 iterations (so if there are 5000 total outer iterations it does 50 parallel.for loops). That stops the not responding and the rest of my code can remain as is. I am also using PPL for now, but will watch this page and see what the OTL answer is. – Some1Else Mar 13 '19 at 22:14