如何在TThread中正确使用Idhttp?

How to use Idhttp within TThread Properly?

我目前有这个 TThread 可以将一些图像下载到桌面,这是线程代码:

type
  TDownloadUpdateAnimateEvent = procedure(Sender: TObject; AAnimationname: String;
    var AAnimationUrl: String) of object;

type
  TDownloadanimation = class(TThread)
    private
      FOnUpdateAnimate: TDownloadUpdateAnimateEvent;
      FAnimationname : String;
      FAnimationUrl : string;
      FPathImage : String;
      ImageName: string;
      PathURL: string;
      FFileNameImage: string;
    procedure DoUpdateAnimate;
    protected
      procedure Execute; override;
    public
      constructor Create(AAnimationname:string; AAnimationUrl: string; AOnUpdateAnimate : TDownloadUpdateAnimateEvent; APathImage : string);
      property PathImage: string read FPathImage;
      property FileNameImage: string read FFileNameImage;
  end;

{ TDownloadanimation }

constructor TDownloadanimation.Create(AAnimationname, AAnimationUrl: string; AOnUpdateAnimate : TDownloadUpdateAnimateEvent; APathImage : string);
var
  URI: TIdURI;
begin
  inherited Create(false);
  FOnUpdateAnimate := AOnUpdateAnimate;
  FPathImage := APathImage;
  FAnimationname := AAnimationname;
  FAnimationUrl := AAnimationUrl;
  URI := TIdURI.Create(FAnimationUrl);
  try
    ImageName := URI.Document;
    PathURL := URI.path;
  finally
    FreeAndNil(URI);
  end;
end;

procedure TDownloadanimation.DoUpdateAnimate;
begin
  if Assigned(FOnUpdateAnimate) then
    FOnUpdateAnimate(self, FAnimationname, FFileNameImage);
end;

procedure TDownloadanimation.Execute;
var
  aMs: TMemoryStream;
  aIdHttp: TIdHttp;
  IdSSL: TIdSSLIOHandlerSocketOpenSSL;
  path: string;
  dir: string;
  SPEXT : String;
  itsimage: string;
  responsechk: Integer;
begin
  dir := AnsiReplaceText(PathURL, '/', '');

  if (ImageName = '') then
  begin
    exit;
  end;

  path := PathImage + ImageName;

  if fileexists(path) then
  begin
    FFileNameImage := path;
    if Assigned(FOnUpdateAnimate) then
    begin
      Synchronize(DoUpdateAnimate);
    end;
    exit;
  end
  else
    if not fileexists(path) then
    begin
      aMs := TMemoryStream.Create;
      aIdHttp := TIdHttp.Create(nil);
      IdSSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
      try
        IdSSL.SSLOptions.Method := sslvTLSv1;
        IdSSL.SSLOptions.Mode := sslmUnassigned;
        aIdHttp.HTTPOptions := [hoForceEncodeParams] + [hoNoProtocolErrorException];
        aIdHttp.IOHandler := IdSSL;
        aIdHttp.AllowCookies := True;
        aIdHttp.Request.UserAgent := 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0';
        aIdHttp.HandleRedirects := True;
        aIdHttp.RedirectMaximum := 3;
        try
          aIdHttp.Head(trim(FAnimationUrl));
        except
        end;
        itsimage := aIdHttp.Response.ContentType;
        responsechk := aIdHttp.ResponseCode;

        if responsechk <> 200 then
        begin
          FFileNameImage := 'error';
          if Assigned(FOnUpdateAnimate) then
          begin
            Synchronize(DoUpdateAnimate);
          end;
          exit;
        end;
        if (itsimage = 'image/gif') then
        begin
          try
            aIdHttp.Get(trim(FAnimationUrl), aMs);
          except
          end;
          aMs.SaveToFile(path);
        end;

        try
          if aIdHttp.Connected then
            aIdHttp.Disconnect;
        except
        end; 

     finally
       FreeAndNil(aMs);
       FreeAndNil(IdSSl);
       FreeAndNil(aIdHttp);
     end;
  end;

  FFileNameImage := path;

  if Assigned(FOnUpdateAnimate) then
  begin
    Synchronize(DoUpdateAnimate);
  end;
end;

这里是调用 Create Thread 的表单

For i := 0 To imageslist.Count-1 do
begin
  Animatref := 'ref';
  Animaturl := imageslist.Strings[i];
  URI := TIdURI.Create(Animaturl);
  try
    ImageName := URI.Document;
  finally
    FreeAndNil(URI);
  end;

  if (ExtractFileExt(ImageName) = '.gif') then
  begin
    if Fileexists(CheckPath+ImageName) then //if image exists then do something and dont start the Thread To download it 
    begin
      //do something
    end 
    else 
    if NOT Fileexists(CheckPath+ImageName) then // not found on desk and start to download
    begin
      addanimation(Animatref, Animaturl);
    end;
  end;
end;


procedure Tform1.addanimation(animationname, animationurl: string);
var
  Pathanimate:string;
begin
  Pathanimate := appfolder;
  Downloadanimation := TDownloadanimation.Create(animationname, animationurl,  UpdateAnimate, Pathanimate);
end;

当我调用应用程序 Destroy 时:

if Assigned(Downloadanimation) then
begin
  Downloadanimation.Terminate;
  FreeAndNil(Downloadanimation);
end;

但是每次我在下载 Thread Fired 后关闭应用程序时都会出现 运行 时间错误。这是因为我同时下载了多张图片吗?如果是的话,有没有更好的方法来编写线程,比如在下载图像完成后等待,如果这是真正的问题,然后开始新的下载。

发出线程终止信号后,在 释放它之前 对其调用 WaitFor():

if Assigned(Downloadanimation) then
begin
  Downloadanimation.Terminate;
  Downloadanimation.WaitFor; // <-- add this
  FreeAndNil(Downloadanimation);
end;

此外,您的表单逻辑有可能同时 运行 多个下载线程,但您只跟踪创建的最后一个线程。而且您不会在它完成时释放它,只有在应用程序退出时才释放它。您应该在每个线程上设置 FreeOnTerminate=True,或者将所有线程存储在 TList 中并使用它们的 OnTerminated 事件来了解每个线程何时完成,以便您可以将其从列出并释放它。

另外,您的线程的 Execute() 逻辑可以通过完全删除对 TIdHTTP.Head() 的调用来稍微减少其网络流量,而是设置 TIdHTTP.Request.Accept 属性 到 'image/gif' 调用 TIdHTTP.Get() 时。如果请求的资源存在但不是 GIF,服务器 应该 报告 406 Not Acceptable 响应,其他任何内容都会报告 HTTP 错误,就像 TIdHTTP.Head() 一样。在此示例中发送 HEAD 请求没有意义。