如何解决是否可以使用AsyncCalls单元创建线程池?
| 我试图使用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.
解决方法
棘手的东西。我进行了一些调试(嗯,相当多的调试),发现第1296行的AsyncCallsEx中的代码块:
Result := TAsyncCallArgRecord.Create(Proc,@Arg).ExecuteAsync;
进一步的挖掘表明,它在System.pas(_IntfCopy)中的接口副本中阻塞
CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
查看同一代码的pascal版本,似乎该行释放了先前存储在destination参数中的引用计数。但是,目的地是在呼叫者(您的代码)中未使用的结果。
现在是棘手的部分。
AsyncCallEx返回一个接口(在您这种情况下),调用方将其丢弃。因此,理论上编译后的代码(以伪形式)应如下所示
loop
tmp := AsyncCallEx(...)
tmp._Release
until
但是编译器对此进行了优化
loop
tmp := AsyncCallEx(...)
until
tmp._Release
为什么?因为它知道分配接口将自动释放存储在tmp变量中的接口的引用计数(对_IntfCopy中_Release的调用)。因此,无需显式调用_Release。
但是释放IAsyncCall会使代码等待线程完成。因此,基本上,您每次调用AsyncCallEx时都等待上一个线程完成...
我不知道如何使用AsyncCalls很好地解决此问题。我尝试了这种方法,但是由于某种原因它无法完全正常工作(对大约50个地址执行ping操作后,程序块将停止运行)。
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;
,如果调用AsyncCallEx()
或任何其他AsyncCalls函数,则将返回IAsyncCall
接口指针。如果其引用计数器达到ѭ8,则基础对象被破坏,这将等待工作线程代码完成。您正在循环中调用“ 6”,因此每次将返回的接口指针分配给相同(隐藏)的变量时,将减小参考计数器的值,从而同步释放先前的异步调用对象。
要解决此问题,只需在表单类中添加一个私有数组IAsyncCall
,如下所示:
private
fASyncCalls: array[byte] of IAsyncCall;
并将返回的接口指针分配给数组元素:
fASyncCalls[i] := AsyncCallEx(@NetBiosLookup,ArrNetbiosTasks[i - 1]);
这将使接口保持活动状态并启用并行执行。
请注意,这只是一般性的想法,您应该添加代码以在调用返回时重置相应的数组元素,并等待所有调用完成后再释放表单。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。