2014-10-01 223 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