2011-04-15 73 views
8

我正在尝试使用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. 
+0

我没有看到任何错误在这里。你确定它不工作? – 2011-04-15 02:05:18

+0

代码在单个新线程中执行,但是它会一个接一个地执行请求,而不是同时执行几个请求。理想情况下,我希望它产生10-50个工作线程,并让这些工作人员完成所有任务,直到没有剩下任务。 – Mick 2011-04-15 03:22:46

回答

4

棘手的东西。我做了一些调试(当然,颇有些调试),并发现,在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; 
+0

谢谢您的详细解答。看起来,AsyncCalls不是这项任务的正确工具。 – Mick 2011-04-15 16:25:43

3

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

若要解决此简单的IAsyncCall私人阵列添加到窗体类,如下所示:

private 
    fASyncCalls: array[byte] of IAsyncCall; 

,并指定返回的接口指针数组元素:

fASyncCalls[i] := AsyncCallEx(@NetBiosLookup, ArrNetbiosTasks[i - 1]); 

这将使接口保持活动状态并启用并行执行。

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

相关问题