2014-10-01 4 views
1

Delphi로 HTTP 서버를 만들었습니다. 서버 응답 시간을 테스트하기 위해 임의의 URL을 생성하는 http 클라이언트 애플리케이션을 만들었습니다. 문제는 내가 서버의 요청을 처리하기 시작한 부분을 처리하는 것입니다. 여기에 내 코드의 일부는 다음과 같습니다HTTP 클라이언트 요청 받기

procedure TPerformanceTestForm.ExecuteURLs; 
var 
    requests: array of TRequestBuilder; 
    i: Integer; 
    Stopwatch: TStopwatch; 
    Elapsed: TTimeSpan; 
begin 
    SetLength(requests, 10); 
    EnterCriticalSection(criticalSection); 
    Stopwatch := TStopwatch.StartNew; 

    for i := 0 to Length(requests) - 1 do 
    begin 
    requests[i] := TRequestBuilder.Create; 
    end; 

    // remove this lines from source in order to execute all threads 
    // for i := 0 to Length(requests) - 1 do 
    // begin 
    // requests[i].Terminate; 
    // end; 

    Elapsed := Stopwatch.Elapsed; 
    Seconds := Elapsed.TotalSeconds; 
    LeaveCriticalSection(criticalSection); 
end; 

procedure TPerformanceTestForm.btnStopQueriesClick(Sender: TObject); 
var 
    i: Integer; 
begin 
    for i := 0 to Length(requests) - 1 do 
    begin 
    // requests[i].WaitFor; // the program crashes 
    requests[i].Free; 
    end; 
end; 

이 TRequestBuilder 클래스의 일부입니다 :

이 절차는 요청을 전송하기 시작하는 실행중인

TRequestBuilder = class(TThread) 
private 
    fHttpClient: TIdHTTP; 
public 
    Constructor Create; reintroduce; 
    procedure Execute; override; 
end; 

Constructor TRequestBuilder.Create; 
begin 
    inherited Create(False); // in order not to start another loop and call start for each instance 
    // FreeOnTerminate := True; // removed this line; see the first answer to know why 
    Self.fHttpClient := TIdHTTP.Create; 
    // HttpWorkBegin and HttWork I get from the first answer 
    Self.fHttpClient.OnWorkBegin := HttpWorkBegin; 
    Self.fHttpClient.OnWork := HttpWork; 
end; 

procedure TRequestBuilder.Execute; 
var 
    request, response: string; 
begin 
    repeat 
    try 
     request := GenerateHttpRequest; 
     response := Self.fHttpClient.Get(request); 
     log.AddJob(request + ' ---> ' + response + ' ---> ' + 
     FormatDateTime('dd.mm.yyyy hh:mm:ss', Now)); 
     except 
     on e: Exception do 
     begin 
     errlog.Add(FormatDateTime('dd.mm.yyyy hh:mm:ss', Now) + ' ---> ' + 
      e.Message); 
     end; 
    end; 
    until (Terminated); 
end; 

// EDIT: change Execute procedure to avoid socket errors (removed the httpClient from class variables): 
procedure TRequestBuilder.Execute; 
var 
    request, response: string; 
    httpClient: TIdHTTP; 
begin 
    repeat 
    try 
     httpClient := TIdHTTP.Create; 

     try 
     request := GenerateHttpRequest; 
     response := httpClient.Get(request); 
     log.AddJob(request + ' ---> ' + response + ' ---> ' + 
      FormatDateTime('dd.mm.yyyy hh:mm:ss', Now)); 
     finally 
     httpClient.Free; 
     end; 
    except 
     on e: Exception do 
     begin 
     errlog.Add(FormatDateTime('dd.mm.yyyy hh:mm:ss', Now) + ' ---> ' + 
      e.Message); 
     end; 
    end; 
    until (Terminated); 
end; 

** 편집 : ** 언제 http 클라이언트를 중지하십시오.이 오류가 발생합니다. App.exe 모듈의 주소 004083A0에서 액세스 위반. 주소 FFFFFFFC의 읽기.

** EDIT : ** ExecutreURLs에서 두 번째 for 루프가 제거되었으므로 프로그램이 제대로 작동합니다 (예외가 발생하는 경우도 있음). 내 질문은 지금 : ExecuteURLs 프로 시저에서 요청을 종료하지 않으면 메모리 누수가 발생합니까?

** 편집 : ** Execute 프로 시저에서 repeat- until 루프를 제거하면 프로그램이 올바르게 작동합니다 (첫 번째 편집의 예외 만 발생 함). repeat- until 루프를 추가하고 btnStopQueries onclick 이벤트에서 제거하면 여러 소켓 오류가 발생합니다.

+0

? 좀 더 구체적이어야합니다. –

+0

@RemyLebeau 오류가 OnFormDestroy에 있었는데 해결했지만 질문을 업데이트하지 않았습니다. –

답변

2

TThread.Terminate()을 호출하면 TThread.Terminated 속성이 설정되고 다른 작업은 수행되지 않습니다. 실제로 스레드를 종료하지는 않습니다. 쓰레드는 주기적으로 Terminated을 확인한 다음 필요할 때 Execute()을 종료합니다. 코드의 어느 곳에서나 Terminated 속성을 사용하지 않으므로 Terminate()을 호출하는 것은 귀하의 예에서는 쓸모가 없습니다.

스레드에 FreeOnTerminate=True을 설정하고 있습니다. 그래서 아니, 당신은 Terminate()을 호출하지 않고 스레드를 유출하지 않습니다. 그들은 TIdHTTP이 끝나면 스스로 자유를 줄 것입니다.

액세스 위반은 하나 이상의 스레드가 단순히 종료되어 메모리에서 벗어나서 Terminate()에 전화 할 수있는 기회가 있기 때문에 발생했을 가능성이 큽니다. FreeOnTerminate를 사용하는 엄지 손가락의 규칙은 스레드의 자신의 코드의 외부 에서 스레드 개체에 액세스해야하는 경우 다음 에서 사용 FreeOnTerminate=True을하지 마십시오 (예 : 당신이 스레드를 추적하고 그들에 Terminate()를 호출하고있는 바와 같이)이다 모든! TThread 개체는 ANY 순간에 메모리에서 사라질 수 있습니다. 이 상황에서 유일하게 절약 할 수있는 점은 TThread.OnTerminate 이벤트를 사용하여 FreeOnTerminate 스레드가 종료 될 때 알림을받을 수 있다는 것입니다. 이 이벤트는 스레드가 자체적으로 해제되기 전에 시작됩니다. 그렇지 않으면 FreeOnTerminate=False을 그대로두고 사용을 마쳤 으면 스레드 개체를 수동으로 해제합니다.

더 안전한 방법은 더이 대신과 같습니다 당신이지고 어떤 오류

procedure TPerformanceTestForm.ExecuteURLs; 
var 
    requests: array of TRequestBuilder; 
    i: Integer; 
    Stopwatch: TStopwatch; 
    Elapsed: TTimeSpan; 
begin 
    SetLength(requests, 10); 
    Stopwatch := TStopwatch.StartNew; 

    for i := 0 to Length(requests) - 1 do 
    begin 
    requests[i] := TRequestBuilder.Create; 
    end; 

    // optional, maybe after a timeout... 
    { 
    for i := 0 to Length(requests) - 1 do 
    begin 
    requests[i].Terminate; 
    end; 
    } 

    for i := 0 to Length(requests) - 1 do 
    begin 
    requests[i].WaitFor; 
    requests[i].Free; 
    end; 

    Elapsed := Stopwatch.Elapsed; 
    Seconds := Elapsed.TotalSeconds; 
end; 

TRequestBuilder = class(TThread) 
private 
    fHttpClient: TIdHTTP; 
    procedure HttpWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64); 
    procedure HttpWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); 
protected 
    procedure Execute; override; 
public 
    constructor Create; reintroduce; 
    destructor Destroy; override; 
end; 

constructor TRequestBuilder.Create; 
begin 
    inherited Create(False); 
    fHttpClient := TIdHTTP.Create; 
    fHttpClient.OnWorkBegin := HttpWorkBegin; 
    fHttpClient.OnWork := HttpWork; 
end; 

destructor TRequestBuilder.Destroy; 
begin 
    fHttpClient.Free; 
    inherited Destroy; 
end; 

procedure TRequestBuilder.HttpWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64); 
begin 
    if Terminated then SysUtils.Abort; 
end; 

procedure TRequestBuilder.HttpWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); 
begin 
    if Terminated then SysUtils.Abort; 
end; 

procedure TRequestBuilder.Execute; 
var 
    request, response: string; 
begin 
    request := 'http://localhost/?command=validcommand&param=value'; 
    response := fHttpClient.Get(request); 
    // log source: http://stackoverflow.com/questions/26099961/asynchronous-append-to-txt-file-in-delphi 
    log.AddJob(request + ' ---> ' + response); 
end; 
+0

요청을 전역 변수로 만들었고 길이 (요청)에 대한 두 번째 루프를 넣었습니다 - 1 do begin 요청 [ 나는].기다립니다; 요청 [i]. 무료; 끝; 다른 버튼의 onclick 이벤트이지만 프로그램이 충돌합니다. 또한 소스에서 몇 가지 변경 사항을 만들었습니다. (편집 된 질문 참조) –

+0

두 번째 Execute 프로 시저를 살펴볼 수 있습니까? 프로 시저를 사용하여 로컬 http 클라이언트 (실행 프로 시저의 로컬)에 대해 OnWorkBegin 및 OnWork 속성을 설정해야합니까? 당신의 대답에서. 설정을하면 로그 파일의 내용에 차이가 있습니다 (응답이있는 일부 쿼리는 클라이언트 로그 파일에 기록되지 않습니다) –

+1

'ExecuteURLs()'내부에 여전히'requests' 변수가 있습니다. 'btnStopQueriesClick()'이 접근하는'requests' 변수를 채우지 않습니다. 그리고 각 스레드에서'Free()'하기 전에'Terminate()'와'WaitFor()'를 호출해야합니다. 아직 실행중인 스레드를 해제하지 마십시오. 그리고 모든 루프 반복에서 'TIdHTTP'를 재 작성할 필요가 없습니다. 'Execute()'의 맨 위에 한 번만 생성하고 (생성자가 아닌 경우) 루프 내부에서 다시 사용하십시오. –