2011-04-15 68 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]); 

這將使接口保持活動狀態並啓用並行執行。

請注意,這只是一般的想法,您應該添加代碼以在調用返回時重置相應的數組元素,並在釋放表單之前等待所有調用完成。

相關問題