Multithreading 是否可以使用AsyncCalls单元创建线程池?

Multithreading 是否可以使用AsyncCalls单元创建线程池?,multithreading,delphi,Multithreading,Delphi,我正在尝试使用在整个C类子网上执行Netbios查找。理想情况下,我希望它同时执行10+个查找,但目前它一次只执行1个查找。我做错了什么 我的表格包含一个按钮和一个备忘录 unit main; interface uses Windows, Messages, SysUtils, Classes, Forms, StdCtrls, AsyncCalls, IdGlobal, IdUDPClient, Controls; type PWMUComm

我正在尝试使用在整个C类子网上执行Netbios查找。理想情况下,我希望它同时执行10+个查找,但目前它一次只执行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.
unitmain;
接口
使用
窗户,
信息,
SysUtils,
班级,
形式,
StdCtrls,
异步调用,
IdGlobal,
IdUDPClient,
控制;
类型
PWMUCommand=^TWMUCommand;
TWMUCommand=记录
主持人:字符串;
ip:字符串;
bOnline:布尔型;
结束;
类型
PNetbiosTask=^TNetbiosTask;
TNetbiosTask=记录
主要形态:桑德尔;
女鞍:绳子;
sHostname:string;
bOnline:布尔型;
iTimeout:整数;
结束;
常数
WM_THRD_SITE_MSG=WM_USER+5;
WM_POSTED_MSG=WM_USER+8;
类型
TForm2=类别(TForm)
按钮1:t按钮;
备忘录1:TMemo;
程序按钮1点击(发送方:ToObject);
私有的
过程线程消息(var Msg:TMessage);消息WM_POSTED_MSG;
{私有声明}
公众的
{公开声明}
结束;
变量
表2:TForm2;
实施
{$R*.dfm}
函数NetBiosLookup(数据:TNetbiosTask):布尔值;
常数
NB#U请求=#$A2#$48#$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_端口=137;
NB_BUFSIZE=8192;
变量
缓冲区:TIdBytes;
I:整数;
RepName:string;
UDP客户端:TIdUDPClient;
msg_prm:PWMUCommand;
开始
RepName:='';
结果:=假;
UDPClient:=无;
UDPClient:=TIdUDPClient.Create(nil);
尝试
尝试
使用UDPClient do
开始
主机:=配平(Data.sAddress);
端口:=NB_端口;
发送(NB_请求);
结束;
设置长度(缓冲区,NB_BUFSIZE);
如果(00;
PostMessage(Data.hMainForm、WM_POSTED_MSG、WM_THRD_SITE_MSG、integer(MSG_prm));
结束;
程序TForm2.按钮1单击(发件人:ToObject);
变量
i:整数;
arrnebiostasks:TNetbiosTask的数组;
sIp:字符串;
开始
//
SetMaxAsyncCallThreads(50);
SetLength(任务数,255);
sIp:=“192.168.1.”;
对于i:=1到255 do
开始
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;
结束;
结束;
过程TForm2.ThreadMessage(var Msg:TMessage);
变量
msg_prm:PWMUCommand;
开始
//
案例Msg.WParam的
WM_THRD_SITE_MSG:
开始
msg_prm:=pwmumommand(msg.LParam);
尝试
备忘录1.Lines.Add(msg_prm.ip+'='+msg_prm.host+'---联机?'+BoolToStr(msg_prm.bOnline));
最后
处置(msg_prm);
结束;
结束;
结束;
结束;
结束。

棘手的事情。我做了一些调试(嗯,相当多的调试),发现第1296行AsyncCallsEx中的代码块:

Result := TAsyncCallArgRecord.Create(Proc, @Arg).ExecuteAsync;
进一步挖掘表明,它在以下位置阻塞了System.pas(_IntfCopy)中的接口副本:

查看相同代码的pascal版本,这一行似乎释放了先前存储在目标参数中的引用计数。然而,Destination是调用方(代码)中未使用的结果

现在是棘手的部分

AsyncCallEx返回调用者丢弃的接口(在您的情况下)。因此,理论上编译代码(伪形式)应该是这样的

loop
  tmp := AsyncCallEx(...)
  tmp._Release
until
但是,编译器会对此进行优化,以

loop
   tmp := AsyncCallEx(...)
until
tmp._Release
为什么??因为它知道分配接口将自动释放tmp变量中存储的接口的引用计数(调用_IntfCopy中的_release)。因此,没有必要明确地称之为“释放”

但是,释放IAsyncCall会导致代码等待线程完成。因此,每次调用AsyncCallEx时,基本上都会等待前一个线程完成

我不知道如何使用异步调用很好地解决这个问题。我尝试过这种方法,但不知何故它并没有完全按照预期工作(ping大约50个地址后程序块)


如果调用
AsyncCallEx()
或任何其他AsyncCalls函数,将返回一个
IAsyncCall
接口指针。如果其引用计数器达到
0
,则基础对象将被销毁,这将等待工作线程代码完成。您正在循环中调用
AsyncCallEx()
,因此每次返回的接口指针都将被分配给同一个(隐藏)变量,从而减少引用计数器,从而同步释放上一个异步调用对象

要解决此问题,只需将
iasyncall
的私有数组添加到form类中,如下所示:

private
  fASyncCalls: array[byte] of IAsyncCall;
并将返回的接口指针分配给数组元素:

fASyncCalls[i] := AsyncCallEx(@NetBiosLookup, ArrNetbiosTasks[i - 1]);
这将使接口保持活动状态并支持并行执行


请注意,这只是一般的想法,您应该添加代码,以便在调用返回时重置相应的数组元素,并等待所有调用完成后再释放表单。

我在这里没有看到任何错误。你确定它不工作吗?代码在一个新线程中执行,但它执行一个接一个的请求
private
  fASyncCalls: array[byte] of IAsyncCall;
fASyncCalls[i] := AsyncCallEx(@NetBiosLookup, ArrNetbiosTasks[i - 1]);