是否可以使用AsyncCalls单元创建线程池?
我正在尝试使用AsyncCalls对整个C类子网执行Netbios查找。理想情况下,我希望它能够同时执行10个以上的查找,但它目前只能查找一次。我在这里做错了什么?是否可以使用AsyncCalls单元创建线程池?
我的表单包含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.
棘手的东西。我做了一些调试(当然,颇有些调试),并发现,在AsyncCallsEx代码块行1296:
Result := TAsyncCallArgRecord.Create(Proc, @Arg).ExecuteAsync;
进一步深挖表明,在
块接口副本中System.pas(_IntfCopy)CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
看着相同代码的pascal版本,看起来这条线释放了先前存储在目标参数中的引用计数。但是,目的地是调用者(您的代码)中未使用的结果。
现在出现棘手的部分。
AsyncCallEx返回一个接口(在你的情况下)调用者抛弃。所以理论上编译后的代码(伪形式)应该是这样
loop
tmp := AsyncCallEx(...)
tmp._Release
until
但是编译器优化这
loop
tmp := AsyncCallEx(...)
until
tmp._Release
为什么?因为它知道分配接口将自动释放存储在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;
谢谢您的详细解答。看起来,AsyncCalls不是这项任务的正确工具。 – Mick 2011-04-15 16:25:43
如果调用AsyncCallEx()
或任何其他的AsyncCalls函数将返回一个IAsyncCall
接口指针。如果其引用计数器达到0
,则底层对象将被销毁,这将等待工作线程代码完成。您在循环中调用AsyncCallEx()
,因此每次将返回的接口指针分配给同一个(隐藏)变量时,递减引用计数器,从而同步释放前一个异步调用对象。
若要解决此简单的IAsyncCall
私人阵列添加到窗体类,如下所示:
private
fASyncCalls: array[byte] of IAsyncCall;
,并指定返回的接口指针数组元素:
fASyncCalls[i] := AsyncCallEx(@NetBiosLookup, ArrNetbiosTasks[i - 1]);
这将使接口保持活动状态并启用并行执行。
请注意,这只是一般的想法,您应该添加代码以在调用返回时重置相应的数组元素,并在释放表单之前等待所有调用完成。
我没有看到任何错误在这里。你确定它不工作? – 2011-04-15 02:05:18
代码在单个新线程中执行,但是它会一个接一个地执行请求,而不是同时执行几个请求。理想情况下,我希望它产生10-50个工作线程,并让这些工作人员完成所有任务,直到没有剩下任务。 – Mick 2011-04-15 03:22:46