Mic*_*ick 8 delphi multithreading
我试图使用AsyncCalls在整个C类子网上执行Netbios查找.理想情况下,我希望它能同时执行10次以上的查找,但目前它一次只进行1次查找.我在这做错了什么?
我的表单包含1个按钮和1个备忘录.
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.
Run Code Online (Sandbox Code Playgroud)
棘手的事情。我做了一些调试(嗯,相当多的调试),发现 AsyncCallsEx 中的代码块位于第 1296 行:
Result := TAsyncCallArgRecord.Create(Proc, @Arg).ExecuteAsync;
Run Code Online (Sandbox Code Playgroud)
进一步挖掘表明它阻塞了 System.pas (_IntfCopy) 中的接口复制
CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
Run Code Online (Sandbox Code Playgroud)
查看相同代码的 pascal 版本,该行似乎释放了先前存储在目标参数中的引用计数。然而,目标是调用者(您的代码)中未使用的结果。
现在是棘手的部分。
AsyncCallEx 返回一个接口(在您的情况下)调用者会丢弃该接口。所以理论上编译后的代码(以伪形式)应该如下所示
loop
tmp := AsyncCallEx(...)
tmp._Release
until
Run Code Online (Sandbox Code Playgroud)
然而编译器将其优化为
loop
tmp := AsyncCallEx(...)
until
tmp._Release
Run Code Online (Sandbox Code Playgroud)
为什么?因为它知道对接口进行赋值会自动释放tmp变量中存储的接口的引用计数(_IntfCopy中对_Release的调用)。所以不需要显式调用_Release。
然而,释放 IAsyncCall 会导致代码等待线程完成。所以基本上每次调用 AsyncCallEx 时都会等待前一个线程完成...
我不知道如何使用 AsyncCalls 很好地解决这个问题。我尝试了这种方法,但不知怎的,它并没有完全按预期工作(在 ping 大约 50 个地址后程序块)。
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;
Run Code Online (Sandbox Code Playgroud)