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.
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 callingApplication.ProcessMessagescan lead to unexpected things to happen.Use the
OnTerminateevent 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):Update
Running the above test in debug mode gave:
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.
To update the GUI within a parallel for loop, just use this code within the loop: