是否可以使用 AsyncCalls 单元创建线程池?
我正在尝试使用 异步调用。理想情况下,我希望它同时执行 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.
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
棘手的事情。我做了一些调试(嗯,相当多的调试),发现 AsyncCallsEx 中的代码在第 1296 行中阻塞:
进一步挖掘表明它在 System.pas (_IntfCopy) 中的接口复制中阻塞,参见
Looking at the pascal version of the same code看来这一行释放了先前存储在目标参数中的引用计数。然而,目标是调用者(您的代码)中未使用的结果。
现在是棘手的部分。
AsyncCallEx 返回一个接口(在您的情况下)调用者会丢弃该接口。因此理论上编译后的代码(以伪形式)应该如下所示
但是编译器将其优化为
为什么?因为它知道对接口进行赋值会自动释放tmp变量中存储的接口的引用计数(_IntfCopy中对_Release的调用)。所以不需要显式调用_Release。
然而,释放 IAsyncCall 会导致代码等待线程完成。所以基本上你每次调用 AsyncCallEx 时都会等待前一个线程完成......
我不知道如何使用 AsyncCalls 很好地解决这个问题。我尝试了这种方法,但不知怎的,它并没有完全按预期工作(在 ping 大约 50 个地址后程序块)。
Tricky stuff. I did some debugging (well, quite some debugging) and found out that the code blocks in AsyncCallsEx in line 1296:
Further digging showed that it blocks in interface copy in System.pas (_IntfCopy) at
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
However the compiler optimizes this to
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).
如果您调用
AsyncCallEx()
或任何其他 AsyncCalls 函数,您将返回一个IAsyncCall
接口指针。如果其引用计数器达到0
,则底层对象将被销毁,这将等待工作线程代码完成。您在循环中调用AsyncCallEx()
,因此每次返回的接口指针都会被分配给同一个(隐藏)变量,从而减少引用计数器,从而同步释放先前的异步调用对象。要解决此问题,只需将
IAsyncCall
的私有数组添加到表单类中,如下所示:并将返回的接口指针分配给数组元素:
这将使接口保持活动状态并启用并行执行。
请注意,这只是一般想法,您应该添加代码以在调用返回时重置相应的数组元素,并在释放表单之前等待所有调用完成。
If you call
AsyncCallEx()
or any other of the AsyncCalls functions you are returned aIAsyncCall
interface pointer. If its reference counter reaches0
the underlying object is destroyed, which will wait for the worker thread code to complete. You are callingAsyncCallEx()
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:and assign the returned interface pointers to the array elements:
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.