2014-10-01 166 views
1

我用Delphi創建了一個HTTP服務器。爲了測試服務器響應時間,我創建了一個生成隨機地址的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。

**編輯:**我刪除了ExecutreURLs中的第二個for循環,現在程序工作正常(有時引發異常)。我現在的問題是:當我沒有終止ExecuteURLs過程中的請求時,程序是否泄漏內存?

**編輯:**當我從執行過程中刪除repeat- until循環時,程序工作正常(僅在第一次編輯中引發異常)。當我添加repeat-until循環並從btnStopQueries onclick事件中刪除時,我得到幾個套接字錯誤

+0

你得到什麼錯誤?你需要更具體。 – 2014-10-02 14:28:52

+0

@RemyLebeau錯誤是在OnFormDestroy和我修復它,但我忘了更新問題 – 2014-10-02 14:36:00

回答

2

調用TThread.Terminate()僅僅設置TThread.Terminated屬性,而不執行其他操作。它並不實際終止線程。一個線程負責定期檢查Terminated,然後在需要時從Execute()退出。您的代碼中沒有使用Terminated屬性,因此在您的示例中調用Terminate()是無用的。

您正在設置FreeOnTerminate=True在你的主題中。所以不,你沒有通過電話Terminate()泄漏線程。在TIdHTTP完成工作後,他們將自行解脫。

您的訪問衝突最有可能是由於一個或多個線程在您有機會致電Terminate()之前終止並釋放自己的內存。根據經驗,使用FreeOnTerminate規則是,如果你需要從訪問線程對象外螺紋自己的代碼(比如你是通過跟蹤線程,並呼籲他們Terminate()做),那麼DO NOT使用FreeOnTerminate=True在所有! TThread對象可能會從任何時刻的內存中消失。在這種情況下,您唯一的優點就是如果您使用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

我提出了一個全局變量的請求,我把第二個循環爲我:= 0到長度(請求) - 1做 開始 請求[一世]。等待; 請求[i] .Free; 結束;作爲另一個按鈕的onclick事件,但程序崩潰。我還對源代碼做了一些修改(請參閱已編輯的問題) – 2014-10-02 09:03:35

+0

您可以在第二個執行過程中查看一下:是否必須使用過程爲本地http客戶端(本地執行過程)設置OnWorkBegin和OnWork屬性從你的答案。當我設置它們時,日誌文件的內容有所不同(有些查詢與它們的響應沒有寫在客戶端日誌文件中) – 2014-10-02 09:43:05

+1

在ExecuteURLs()中你仍然有一個本地'requests'變量,不填充'btnStopQueriesClick()'訪問的'requests'變量。你需要在每個線程上調用'Terminate()'和'WaitFor()',然後'Free()'它。切勿釋放仍在運行的線程。並且您不需要在每次循環迭代中重新創建'TIdHTTP'。在Execute()的頂部創建一次(如果不在構造函數中),然後在循環中重用它。 – 2014-10-02 14:26:53

相關問題