iTask - how to use variables as parameters to TTask procedure - multithreading

I need to create a number of iTasks that will populate the same array in different positions. Since the code to be performed for each Task is the same, I decided to create an array of iTasks and created 4 tasks. I got a problem when passing parameters to the major procedure inside the iTask. when I use variables as parameters , only the values of the last Task created are being considered. When I pass the parameters as values (hard-coded) it respect all values for each task. Please see my code :
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
UNTThreads, Vcl.StdCtrls,
System.Threading ;
type
Vet = array of integer;
type
TFMThreadArray = class(TForm)
EDTArraySize: TEdit;
EDTNumberofThreads: TEdit;
Memo1: TMemo;
LBArraySize: TLabel;
LBThreads: TLabel;
BTUsingForLoop: TButton;
EDTThread: TEdit;
BTHardCoded: TButton;
procedure BTUsingForLoopClick(Sender: TObject);
procedure BTHardCodedClick(Sender: TObject);
private
{ Private declarations }
procedure ProcA ( Const pin, pfin, Psize, Ptask : integer;
Var Parray : vet);
public
{ Public declarations }
end;
var
FMThreadArray: TFMThreadArray;
implementation
{$R *.dfm}
// Procedure to be called by each iTask
procedure TFMThreadArray.ProcA ( Const pin, pfin, Psize, Ptask : integer;
Var Parray : vet);
var
vind : integer;
begin
for vind := pin to pfin do
begin
Parray[vind] := vind * 10;
end;
end;
==> This below method, BTHardCodedClick, produces the expected result. It populates the array accordingly. BUT it is hard coded in creating 4 iTasks and in passing parameters in ProcA. I don't want to implement this way !
procedure TFMThreadArray.BTHardCodedClick(Sender: TObject);
var
varray : vet;
ind, indtask : Integer;
Ptasks : array of iTask;
begin
memo1.Clear;
SetLength(PTasks,Strtoint(EDTNumberofThreads.text));
SetLength(varray,StrToint(EDTarraysize.text));
// fill array with a initial value -2
for ind := Low(varray) to High(varray) do
varray[ind] :=-2;
// when call ProcA passing values parameters it works propperly
PTasks[0] := TTask.Create( procedure
begin
ProcA(0,3,16,0,varray) ;
end
) ;
PTasks[1] := TTask.Create( procedure
begin
ProcA(4,7,16,1,varray) ;
end
) ;
PTasks[2] := TTask.Create( procedure
begin
ProcA(8,11,16,2,varray) ;
end
) ;
PTasks[3] := TTask.Create( procedure
begin
ProcA(12,15,16,3,varray) ;
end
) ;
for Indtask := Low(Ptasks) to High(Ptasks) do
Ptasks[Indtask].Start;
TTask.WaitForAll(Ptasks);
memo1.Clear;
memo1.Lines.Add(' ============== Creating TASKs with hard-coded parameters ===============');
memo1.lines.add(' Array size : ' + EDTArraySize.text +
' number of Tasks : ' + EDTNumberofThreads.text);
memo1.Lines.Add(' =========================================================');
for ind := Low(varray) to High(varray) do
memo1.Lines.Add(' Array position : ' + Format('%.3d',[ind]) +
' content : ' + varray[ind].ToString );
end;
===> The following method is the one I want to implement BUT it is not working !, because it is not populating the array. It seems that only the last iTask " PTasks[indtask]" is being performed.
procedure TFMThreadArray.BTUsingForLoopClick(Sender: TObject);
var
varray : vet;
Ptasks : array of iTask;
vind, indtask, vslice : Integer;
vfirst, vlast, vthreads, vsize : Integer;
begin
vthreads := Strtoint(EDTNumberofThreads.text);
vsize := StrToint(EDTArraysize.text);
SetLength(PTasks,vthreads);
SetLength(varray,vsize);
for vind := Low(varray) to High(varray) do
varray[vind]:=-33;
vslice := Length(varray) div vthreads;
for indtask := Low(PTasks) to High(PTasks) do
begin
vfirst := indtask * vslice;
vlast := (indtask + 1) * vslice - 1;
if (Length(varray) mod vthreads <> 0) and (indtask = High(Ptasks)) then
vlast := HIgh(varray);
PTasks[indtask] := TTask.Create( procedure
begin
procA(vfirst,vlast,vsize,indtask,varray) ;
end
) ;
end;
// Starting all Tasks
for Indtask := Low(Ptasks) to High(Ptasks) do
Ptasks[Indtask].Start;
// Waits until all Tasks been concluded
TTask.WaitForAll(Ptasks);
memo1.Clear;
memo1.Lines.Add(' ============= Using For Loop to create the TASKs =====================');
memo1.lines.add(' Array size : ' + EDTArraySize.text +
' number of Tasks : ' + EDTNumberofThreads.text);
memo1.Lines.Add(' =========================================================');
for vind := Low(varray) to High(varray) do
memo1.Lines.Add(' Array position : ' + Format('%.3d',[vind]) +
' content : ' + varray[vind].ToString );
end;
end.
I can't understand why a call to procA(vfirst,vlast,vsize,indtask,varray) inside the iTask is not considering the values of parameters vfirst, vlast.
Thanks in advance for your help !

The effect you are observing is due to anonymous method variable capture mechanism. It does not capture variable values at specific point during code execution, but location of the variables.
Since all tasks run after the loop where you create them, you will see only the last value stored.
To solve your problem you have to add additional function ensuring that you don't capture common variables in your task.
function CreateTask(vfirst, vlast, vsize, indtask: integer; var varray: Vet): ITask;
var
va: Vet;
begin
// var parameter cannot be captured so we have to store it into
// local variable - dynamic arrays act like pointers and any changes
// to local variable will actually change the original too
va := varray;
Result := TTask.Create(
procedure
begin
ProcA(vfirst, vlast, vsize, indtask, va);
end);
end;
And then you call it like
Ptasks[indtask] := CreateTask(vfirst, vlast, vsize, indtask, varray);
Of course, you can also remove your ProcA procedure and incorporate its logic directly inside CreateTask function if that suits your needs.

Related

Delphi PPL TTask Procedure with Parameters

I do not know how to Create a TTask where I have a Procedure with parameters, without parameters it works but with parameters it does not .
Example
procedure TMain.SYNC(AProgressBar: TProgressBar; ASleep: Integer);
var i : integer;
begin
for i := 0 to 100 do
begin
sleep(ASleep);
TThread.Queue(TThread.CurrentThread,
procedure
begin
AProgressBar.Position:=i;
end);
end;
end;
Then I would like to create 4 Tasks like this :
setlength(Tasks,4);
Tasks[0] := TTask.Create(SYNC(progressThread1,100));
Tasks[1] := TTask.Create(SYNC(progressThread2,150));
Tasks[2] := TTask.Create(SYNC(progressThread3,300));
Tasks[3] := TTask.Create(SYNC(progressThread4,200));
Tasks[0].Start;
Tasks[1].Start;
Tasks[2].Start;
Tasks[3].Start;
TTask operates with anonymous procedures. You can capture the values that you want to pass to your method, eg:
SetLength(Tasks, 4);
Tasks[0] := TTask.Create(
procedure
begin
SYNC(progressThread1, 100);
end
);
Tasks[1] := TTask.Create(
procedure
begin
SYNC(progressThread2, 150);
end
);
Tasks[2] := TTask.Create(
procedure
begin
SYNC(progressThread3, 300);
end
);
Tasks[3] := TTask.Create(
procedure
begin
SYNC(progressThread4, 200);
end
);
Tasks[0].Start;
Tasks[1].Start;
Tasks[2].Start;
Tasks[3].Start;
Extending the Remy's answer, you can also write a function which returns an anonymous function that you pass to the task.
function MakeSync(AProgressBar: TProgressBar; ASleep: integer): TProc;
begin
Result :=
procedure
begin
SYNC(AProgressBar, ASleep);
end;
end;
SetLength(Tasks, 4);
Tasks[0] := TTask.Create(MakeSYNC(progressThread1, 100));
Tasks[1] := TTask.Create(MakeSYNC(progressThread2, 150));
Tasks[2] := TTask.Create(MakeSYNC(progressThread3, 300));
Tasks[3] := TTask.Create(MakeSYNC(progressThread4, 200));
Tasks[0].Start;
Tasks[1].Start;
Tasks[2].Start;
Tasks[3].Start;
Extending Remy's answer.
The loop 0..100 calling TThread.Queue with index i suffers from updating the progressbar with the i reference value, rather than the passed value.
To better view the consequence of this, remove the sleep call and add the i value to a memo. This will reveal a sequence similar to this:
42
101
101
101
...
101
Here is an example of how to capture the value of i when calling TThread.Queue:
procedure TMain.SYNC(AProgressBar: TProgressBar; ASleep: Integer);
function CaptureValue( ix : Integer) : TThreadProcedure;
begin
Result :=
procedure
begin
AProgressBar.Position := ix;
end;
end;
var i : integer;
begin
for i := 0 to 100 do
begin
sleep(ASleep);
TThread.Queue(TThread.CurrentThread, CaptureValue(i) );
end;
end;

Delphi tfilestream.readbuffer fails to read string value from file

I am reading and writing data from a file using a filestream but am having a problem reading strings from my file.
In a test VCL form program I have written:
procedure tform1.ReadfromFile4;
var
fs: TFileStream;
arrayString: Array of String;
i, Len1 : Cardinal;
// s : string;
begin
fs := TFileStream.Create('C:\Users\Joe\Documents\Delphi\Streamtest.tst',
fmOpenRead or fmShareDenyWrite);
Memo1.lines.clear;
try
fs.ReadBuffer(Len1, SizeOf(Len1));
SetLength(arrayString, Len1);
FOR i := 0 to Len1-1 do begin
fs.ReadBuffer(Len1, SizeOf(Len1));
SetLength(arrayString[i], Len1);
Fs.ReadBuffer(arrayString[i], Len1);
memo1.lines.add (arrayString[i]);
end;
finally
fs.free;
end;
end;
procedure tform1.WriteToFile4;
var
fs: TFileStream;
arrayString: Array of String;
Len1, c, i: Cardinal;
begin
Memo1.lines.clear;
SetLength(arrayString, 4);
arrayString[0] := 'First string in this Array';
arrayString[1] := 'the Second Array string';
arrayString[2] := 'String number three of this Array';
arrayString[3] := 'this is the fourth String';
fs := TFileStream.Create('C:\Users\Joe\Documents\Delphi\Streamtest.tst',
fmCreate or fmOpenWrite or fmShareDenyWrite);
try
c := Length(arrayString);
Fs.WriteBuffer(c, SizeOf(c));
for i := 0 to c-1 do begin
Len1 := Length(arrayString[i]);
fs.WriteBuffer(Len1, SizeOf(Len1));
if Len1 > 0 then begin
fs.WriteBuffer(arrayString[i], Len1);
end;
end;
finally
fs.free;
end;
end;
The Save button action enters the four strings correctly, but the Load button (readFromFile4) fails to load the strings from the file. Using the Watch list, I find that the string lengths are set correctly for each string, but the data accessed is not the correct string values. I believe I am faithfully following the instructions on the website : http://www.angelfire.com/hi5/delphizeus/customfiles.html]1 in the section titled
Writing and Reading Dynamic Arrays of Non-Fixed Size Variables
Can anyone shed light on why this does not read the strings from the file correctly?

How do I write a Delphi procedure that modifies a string that works for both PCHAR and string?

To take an example, lets say I would like to write a simple procedure that deletes the 'X' characters from a string.
How can I design my procedure so that it works for both string and PCHAR parameters.
If I define it as:
procedure RemoveX( source : PCHAR);
than calls to RemoveX(PCHAR(mystring)) where myString is a string will remove the 'X' but will not take care of updating the string length ... Hence a subsequent myString := myString + 'done' will leave myString unchanged. And I don't want to change the length after the call to RemoveX, I expect the RemoveX procedure to deal with everything.
If on the other hand I define it as:
procedure RemoveX( var source : string);
I don't know how to pass it a PCHAR ...
I would not suggest implementing the string version in terms of the PChar version, or vice versa. I would keep them separate so that you can tailor them independently, eg:
procedure RemoveX(source : PChar); overload;
procedure RemoveX(var source : string); overload;
procedure RemoveX(source : PChar);
var
P: PChar;
Len: Integer;
begin
if source = nil then Exit;
Len := StrLen(source);
repeat
P := StrScan(source, 'X');
if P = nil then Exit;
StrMove(P, P+1, Len - Integer(P-source));
Dec(Len);
source := P;
until False;
end;
procedure RemoveX(var source : string);
begin
source := StringReplace(source, 'X', '', [rfReplaceAll]);
end;
Update: If you really want to use a single implementation for both PChar and String inputs then you can do something like this:
function RemoveX(source : PChar; sourceLen: Integer): Integer; overload;
procedure RemoveX(source : PChar); overload;
procedure RemoveX(var source : string); overload;
function RemoveX(source : PChar; sourceLen: Integer): Integer;
var
P: PChar;
begin
Result := 0;
if (source = nil) or (sourceLen = 0) then Exit;
repeat
P := StrScan(source, 'X');
if P = nil then Exit;
StrMove(P, P+1, sourceLen - Integer(P-source));
Dec(sourceLen);
source := P;
until False;
Result := sourceLen;
end;
procedure RemoveX(source : PChar);
begin
RemoveX(source, StrLen(source));
end;
procedure RemoveX(var source : string);
begin
UniqueString(source);
SetLength(source, RemoveX(PChar(source), Length(source)));
end;
You cannot implement this using a single parameter. You have two different types.
You could build the string version on top of a PChar version.
procedure RemoveX(var str: string);
var
P: PChar;
begin
UniqueString(str);
P := PChar(str);
RemoveX(P);
str := P;
end;
An alternative for final line could be:
SetLength(str, StrLen(P));
Either way, this obviously assumes that you already have a functioning overload that operates on PChar. And that the function removes characters. Clearly it cannot extend the PChar buffer.
The call to UniqueString is needed in case the string is shared (ref count greater than one) or constant. After this call the string buffer is editable and not shared.
Whether or not avoiding duplication of implementation in this way is the best approach I cannot say. It depends on your design drivers. If simplicity and clarity of code is key, then avoiding duplication makes sense. If performance is key then it may be desirable to provide two bespoke implementations.

Multithreaded bubblesort. Works fine with delphi 7 but not with Lazarus? Compiler bug?

First of all I would like to show you my code:
unit BSort;
{==============================================================================}
{$mode objfpc}{$H+}
{==============================================================================}
interface
{==============================================================================}
uses
Classes, SysUtils;
{==============================================================================}
type
TcompFunc = function(AValue1, AValue2 : Integer) : boolean;
TIntegerArray = array of integer;
PIntegerArray = ^TIntegerArray;
{==============================================================================}
procedure BubbleSort(var AMatrix : TIntegerArray; ACompFunc : TCompFunc);
function V1LargerV2(AValue1, AValue2 : Integer) : Boolean;
{==============================================================================}
implementation
{==============================================================================}
procedure Swap(var AValue1, AValue2 : Integer);
var
Tmp : Integer;
begin
Tmp := AValue1;
AValue1 := AValue2;
AValue2 := Tmp;
end;
{==============================================================================}
function V1LargerV2(AValue1, AValue2 : Integer) : Boolean;
begin
result := AValue1 > AValue2;
end;
{------------------------------------------------------------------------------}
procedure BubbleSort(var AMatrix : TIntegerArray; ACompFunc : TCompFunc);
var
i,j : Word;
begin
for i := Low(AMatrix) to High(AMatrix) - 1 do
for j := Low(AMatrix) to High(AMatrix) - 1 do
begin
if ACompFunc(AMatrix[j], AMatrix[j+1]) then
Swap(AMatrix[j], AMatrix[j+1]);
end;
end;
{==============================================================================}
end.
unit MultiThreadSort;
{==============================================================================}
{$mode objfpc}{$H+}
{==============================================================================}
interface
{==============================================================================}
uses
Classes, SysUtils, BSort;
{==============================================================================}
type
TSortThread = class(TThread)
FMatrix : PIntegerArray;
protected
procedure Execute; override;
public
constructor Create(var AMatrix : TIntegerArray);
public
property Terminated;
end;
{==============================================================================}
implementation
{==============================================================================}
constructor TSortThread.Create(var AMatrix : TIntegerArray);
begin
inherited Create(False);
FreeOnTerminate := False;
FMatrix := #AMatrix;
end;
{------------------------------------------------------------------------------}
procedure TSortThread.Execute;
begin
BubbleSort(FMatrix^, #V1LargerV2);
end;
{==============================================================================}
end.
program sortuj;
{==============================================================================}
{$mode objfpc}{$H+}
{==============================================================================}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils, MultiThreadSort, BSort, Crt;
{==============================================================================}
const
Zakres = 20;
{==============================================================================}
var
Start : Double;
Stop : Double;
Time : array[0..1] of Double;
Matrix : array[0..9] of TIntegerArray;
i,j : Word;
{==============================================================================}
procedure Sort(var AMatrix : TIntegerArray);
var
SortThread : array[0..1] of TSortThread;
Matrix : array[0..1] of TIntegerArray;
Highest : Integer;
i, j, k : Word;
begin
// Znalezienie największej liczby w tablicy.
Highest := Low(Integer);
for i := Low(AMatrix) to High(AMatrix) do
if AMatrix[i] > Highest then
Highest := AMatrix[i];
// Zerowanie tablic pomocniczych.
for i := 0 to 1 do
SetLength(Matrix[i], 0);
// Podział tablicy do sortowania na dwie tablice:
// - pierwsza od najniższej do połowy najwyższej liczby.
// - druga od połowy najwyższej do najwyższej liczby.
j := 0;
k := 0;
for i := Low(AMatrix) to High(AMatrix) do
if AMatrix[i] < Highest div 2 then
begin
SetLength(Matrix[0], Length(Matrix[0]) + 1);
Matrix[0,j] := AMatrix[i];
Inc(j);
end
else
begin
SetLength(Matrix[1], Length(Matrix[1]) + 1);
Matrix[1,k] := AMatrix[i];
Inc(k);
end;
//Tworzenie i start wątków sortujacych.
for i := 0 to 1 do
SortThread[i] := TSortThread.Create(Matrix[i]);
// Oczekiwanie na zakończenie watków sortujących.
//for i := 0 to 1 do
// SortThread[i].WaitFor;
// while not SortThread[i].Terminated do
// sleep(2);
Sleep(10);
SortThread[0].WaitFor;
Sleep(10);
SortThread[1].WaitFor;
Sleep(10);
// Zwalnianie wątków sortujacych.
for i := 0 to 1 do
FreeAndNil(SortThread[i]);
// Łączenie tablic pomocniczych w jedną.
k := 0;
for i := 0 to 1 do
for j := Low(Matrix[i]) to High(Matrix[i]) do
begin
AMatrix[k] := Matrix[i,j];
Inc(k);
end;
end;
{==============================================================================}
begin
Randomize;
ClrScr;
for i := 0 to 9 do
begin
SetLength(Matrix[i],Zakres);
Write('Losowanie ', i, ' tablicy...');
for j := 0 to Zakres - 1 do
Matrix[i,j] := Random(100) - 50;
Writeln('Wylosowana');
end;
Writeln;
Start := TimeStampToMsecs(DateTimeToTimeStamp(Now));
for i := 0 to 9 do
begin
Write('Sortowanie ', i, ' tablicy...');
BubbleSort(Matrix[i],#V1LargerV2);
Writeln('Posortowana');
end;
Stop := TimeStampToMsecs(DateTimeToTimeStamp(Now));
Time[0] := Stop - Start;
Writeln;
for i := 0 to 9 do
begin
Write('Losowanie ',i,' tablicy...');
for j := 0 to Zakres do
Matrix[i,j] := Random(100) - 50;
Writeln('Wylosowana');
end;
Writeln;
Start := TimeStampToMsecs(DateTimeToTimeStamp(Now));
for i := 0 to 9 do
begin
Write('Sortowanie dwuwatkowe ', i, ' tablicy...');
Sort(Matrix[i]);
Writeln('Posortowana');
end;
Stop := TimeStampToMsecs(DateTimeToTimeStamp(Now));
Time[1] := Stop - Start;
Writeln;
Writeln('Sortowanie bąbelkowe : ',Time[0]);
Writeln('Sortowanie dwuwatkowe: ',Time[1]);
Readln;
end.
When I compile that code and run with Delphi 7 it is working fine. But when I compile it with Lazarus the last "writeln" text is doubled or tripled and program hangs. Could someone tell me why?
Delphi 7 is correct:
Lazarus is not correct:
This seems like a bug in FPC. To narrow down the problem it often helps to eliminate code and try to create a minimal example. This, for example, demonstrates the problem :
program project1;
uses
Classes, Crt;
type
TSortThread = class(TThread)
protected
procedure Execute; override;
public
constructor Create;
end;
constructor TSortThread.Create;
begin
inherited Create(False);
FreeOnTerminate := False;
end;
procedure TSortThread.Execute;
begin
end;
var
SortThread : TSortThread;
begin
Write('test ...');
SortThread := TSortThread.Create;
Writeln('created');
SortThread.WaitFor;
SortThread.Free;
Writeln('complete');
Readln;
end.
and produces output:
This seems like a bug in the console output only. Your original program, although it could certainly be improved in a sizeable number of ways, otherwise seems to sort the matrices correctly. This type of bug nevertheless does not inspire confidence in the FPC...
#user246408 Yes u re right the problem is CRT unit. i removed it from uses section and code started to work correctly.

Is it possible to create a thread pool using AsyncCalls unit?

I am attempting to perform a Netbios lookup on an entire class C subnet using AsyncCalls. Ideally I'd like it to perform 10+ lookups concurrently but it currently only does 1 lookup at a time. What am I doing wrong here?
My form contains 1 button and 1 memo.
unit main;
interface
uses
Windows,
Messages,
SysUtils,
Classes,
Forms,
StdCtrls,
AsyncCalls,
IdGlobal,
IdUDPClient,
Controls;
type
PWMUCommand = ^TWMUCommand;
TWMUCommand = record
host: string;
ip: string;
bOnline: boolean;
end;
type
PNetbiosTask = ^TNetbiosTask;
TNetbiosTask = record
hMainForm: THandle;
sAddress: string;
sHostname: string;
bOnline: boolean;
iTimeout: Integer;
end;
const
WM_THRD_SITE_MSG = WM_USER + 5;
WM_POSTED_MSG = WM_USER + 8;
type
TForm2 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
procedure ThreadMessage(var Msg: TMessage); message WM_POSTED_MSG;
{ Private declarations }
public
{ Public declarations }
end;
var
Form2 : TForm2;
implementation
{$R *.dfm}
function NetBiosLookup(Data: TNetbiosTask): boolean;
const
NB_REQUEST = #$A2#$48#$00#$00#$00#$01#$00#$00 +
#$00#$00#$00#$00#$20#$43#$4B#$41 +
#$41#$41#$41#$41#$41#$41#$41#$41 +
#$41#$41#$41#$41#$41#$41#$41#$41 +
#$41#$41#$41#$41#$41#$41#$41#$41 +
#$41#$41#$41#$41#$41#$00#$00#$21 +
#$00#$01;
NB_PORT = 137;
NB_BUFSIZE = 8192;
var
Buffer : TIdBytes;
I : Integer;
RepName : string;
UDPClient : TIdUDPClient;
msg_prm : PWMUCommand;
begin
RepName := '';
Result := False;
UDPClient := nil;
UDPClient := TIdUDPClient.Create(nil);
try
try
with UDPClient do
begin
Host := Trim(Data.sAddress);
Port := NB_PORT;
Send(NB_REQUEST);
end;
SetLength(Buffer, NB_BUFSIZE);
if (0 < UDPClient.ReceiveBuffer(Buffer, Data.iTimeout)) then
begin
for I := 1 to 15 do
RepName := RepName + Chr(Buffer[56 + I]);
RepName := Trim(RepName);
Data.sHostname := RepName;
Result := True;
end;
except
Result := False;
end;
finally
if Assigned(UDPClient) then
FreeAndNil(UDPClient);
end;
New(msg_prm);
msg_prm.host := RepName;
msg_prm.ip := Data.sAddress;
msg_prm.bOnline := Length(RepName) > 0;
PostMessage(Data.hMainForm, WM_POSTED_MSG, WM_THRD_SITE_MSG, integer(msg_prm));
end;
procedure TForm2.Button1Click(Sender: TObject);
var
i : integer;
ArrNetbiosTasks : array of TNetbiosTask;
sIp : string;
begin
//
SetMaxAsyncCallThreads(50);
SetLength(ArrNetbiosTasks, 255);
sIp := '192.168.1.';
for i := 1 to 255 do
begin
ArrNetbiosTasks[i - 1].hMainForm := Self.Handle;
ArrNetbiosTasks[i - 1].sAddress := Concat(sIp, IntToStr(i));
ArrNetbiosTasks[i - 1].iTimeout := 5000;
AsyncCallEx(#NetBiosLookup, ArrNetbiosTasks[i - 1]);
Application.ProcessMessages;
end;
end;
procedure TForm2.ThreadMessage(var Msg: TMessage);
var
msg_prm : PWMUCommand;
begin
//
case Msg.WParam of
WM_THRD_SITE_MSG:
begin
msg_prm := PWMUCommand(Msg.LParam);
try
Memo1.Lines.Add(msg_prm.ip + ' = ' + msg_prm.host + ' --- Online? ' + BoolToStr(msg_prm.bOnline));
finally
Dispose(msg_prm);
end;
end;
end;
end;
end.
Tricky stuff. I did some debugging (well, quite some debugging) and found out that the code blocks in AsyncCallsEx in line 1296:
Result := TAsyncCallArgRecord.Create(Proc, #Arg).ExecuteAsync;
Further digging showed that it blocks in interface copy in System.pas (_IntfCopy) at
CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
Looking at the pascal version of the same code it seems that this line release the reference count stored previously in the destination parameter. Destination, however, is a Result which is not used in the caller (your code).
Now comes the tricky part.
AsyncCallEx returns an interface which (in you case) the caller throws away. So in theory the compiled code (in pseudo form) should look like this
loop
tmp := AsyncCallEx(...)
tmp._Release
until
However the compiler optimizes this to
loop
tmp := AsyncCallEx(...)
until
tmp._Release
Why? Because it knows that assigning the interface will release the reference count of the interface stored in the tmp variable automatically (the call to _Release in _IntfCopy). So there's no need to explicitely call _Release.
Releasing the IAsyncCall however causes the code to wait on thread completion. So basically you wait for the previous thread to complete each time you call AsyncCallEx ...
I don't know how to nicely solve this using AsyncCalls. I tried this approach but somehow it is not working completely as expected (program blocks after pinging about 50 addresses).
type
TNetbiosTask = record
//... as before ...
thread: IAsyncCall;
end;
for i := 1 to 255 do
begin
ArrNetbiosTasks[i - 1].hMainForm := Self.Handle;
ArrNetbiosTasks[i - 1].sAddress := Concat(sIp, IntToStr(i));
ArrNetbiosTasks[i - 1].iTimeout := 5000;
ArrNetbiosTasks[i - 1].thread := AsyncCallEx(#NetBiosLookup, ArrNetbiosTasks[i - 1]);
Application.ProcessMessages;
end;
for i := 1 to 255 do // wait on all threads
ArrNetbiosTasks[i - 1].thread := nil;
If you call AsyncCallEx() or any other of the AsyncCalls functions you are returned a IAsyncCall interface pointer. If its reference counter reaches 0 the underlying object is destroyed, which will wait for the worker thread code to complete. You are calling AsyncCallEx() in a loop, so each time the returned interface pointer will be assigned to the same (hidden) variable, decrementing the reference counter and thus synchronously freeing the previous asynchronous call object.
To work around this simply add a private array of IAsyncCall to the form class, like so:
private
fASyncCalls: array[byte] of IAsyncCall;
and assign the returned interface pointers to the array elements:
fASyncCalls[i] := AsyncCallEx(#NetBiosLookup, ArrNetbiosTasks[i - 1]);
This will keep the interfaces alive and enable parallel execution.
Note that this is just the general idea, you should add code to reset the corresponding array element when a call returns, and wait for all calls to complete before you free the form.

Resources