2011-04-15 4 views
8

AsyncCalls을 사용하여 전체 클래스 C 서브넷에서 Netbios 조회를 수행하려고합니다. 이상적으로 나는 10 + 조회를 동시에 수행하고 싶지만 현재는 한 번에 1 개의 조회 만 수행합니다. 여기서 내가 뭘 잘못하고 있니?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

여기에 오류가 표시되지 않습니다. 그게 효과가 없다고 확신합니까? –

+0

코드는 하나의 새 스레드에서 실행되지만 동시에 여러 개를 실행하는 대신 하나의 요청을 처리합니다. 이상적으로는 10-50 개의 작업 스레드를 생성하고 나머지 작업이 없을 때까지 모든 작업을 완료하도록하십시오. – Mick

답변

4

까다로운 내용. 나는 몇 가지 디버깅을했다 (물론, 꽤 디버깅) 및 라인 1296에서 AsyncCallsEx의 코드 블록이 있음을 발견 :

Result := TAsyncCallArgRecord.Create(Proc, @Arg).ExecuteAsync; 

또한 파고가 보여 주었다

에서 System.pas (_IntfCopy)의 인터페이스 사본에서 차단
CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release 

동일한 코드의 파스칼 버전을 보면이 행은 이전에 대상 매개 변수에 저장된 참조 횟수를 해제 한 것으로 보입니다. 그러나 대상은 호출자 (코드)에서 사용되지 않은 결과입니다.

이제 까다로운 부분이 있습니다.

AsyncCallEx는 발신자가 버려지는 인터페이스를 반환합니다. 컴파일러는

loop 
    tmp := AsyncCallEx(...) 
until 
tmp._Release 

왜이 최적화 그러나 그래서 이론 (의사 형태로) 컴파일 된 코드는

loop 
    tmp := AsyncCallEx(...) 
    tmp._Release 
until 

같이해야합니까? 인터페이스를 할당하면 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; 
+0

자세한 답변 해 주셔서 감사합니다. AsyncCalls는이 작업에 적합한 도구가 아닙니다. – Mick

3

당신이 AsyncCallEx() 전화 또는 AsyncCalls 기능의 다른 당신이 IAsyncCall 인터페이스 포인터를 반환하는 경우. 참조 카운터가 0에 도달하면 기본 개체가 파괴되어 작업자 스레드 코드가 완료 될 때까지 대기합니다. 반복적으로 AsyncCallEx()을 호출하므로 반환 된 인터페이스 포인터가 동일한 (숨겨진) 변수에 할당 될 때마다 참조 카운터가 감소하여 이전의 비동기 호출 객체가 동 기적으로 해제됩니다. 배열 요소에 반환 된 인터페이스 포인터를

private 
    fASyncCalls: array[byte] of IAsyncCall; 

및 지정 :

간단히과 같이, 폼 클래스에 IAsyncCall의 개인 배열을 추가이 문제를 해결하려면

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

이 의지 인터페이스를 활성 상태로 유지하고 병렬 실행을 가능하게하십시오.

이것은 일반적인 아이디어 일 뿐이며 호출이 반환 될 때 해당 배열 요소를 다시 설정하는 코드를 추가하고 폼을 해제하기 전에 모든 호출이 완료되기를 기다려야합니다.

관련 문제