【问题标题】:TIdHttp freezes when the internet gets slower当互联网变慢时,TIdHttp 冻结
【发布时间】:2014-01-20 03:28:50
【问题描述】:

如何避免在互联网变慢或无连接时冻结 idHTTP。我的应用程序被冻结,我什至无法关闭表单。

这就是我设置代码的方式

procedure TDownloader.IdHTTPWork(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCount: Int64);
var
  lwElapsedMS: LongWord;
  iBytesTransferred: Int64;
  iBytesPerSec: Int64;
  iRemaining: Integer;
begin
  if AWorkMode <> wmRead then Exit;

  lwElapsedMS := GetTickDiff(FLastTicks, Ticks);
  if lwElapsedMS = 0 then lwElapsedMS := 1; // avoid EDivByZero error

  if FTotalBytes > 0 then
    FPercentDone := Round(AWorkCount / FTotalBytes * 100.0)
  else
    FPercentDone := 0;

  iBytesTransferred := AWorkCount - FLastWorkCount;

  iBytesPerSec := Round(iBytesTransferred * 1000 / lwElapsedMS);

  if Assigned(OnDownloadProgress) then
  begin
    if FContinueDownload <> 0 then //previous file downloaded
    begin
      iRemaining := 100 - FContinueDownload;
      iRemaining := Round(FPercentDone * iRemaining / 100);
      OnDownloadProgress(Self, FContinueDownload + iRemaining, AWorkCount, FTotalBytes, iBytesPerSec);
    end else
      OnDownloadProgress(Self, FPercentDone, AWorkCount, FTotalBytes, iBytesPerSec);
  end;

  FLastWorkCount := AWorkCount;
  FLastTicks := Ticks;

  if FCancel then
  begin
    Abort;
    TidHttp(ASender).Disconnect;
  end;
end;

procedure TDownloader.IdHTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCountMax: Int64);
begin
  if AWorkMode <> wmRead then Exit;

  FPercentDone := 0;
  FTotalBytes := AWorkCountMax;
  FLastWorkCount := 0;
  FLastTicks := Ticks;
end;

procedure TDownloader.IdHTTPWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
  if AWorkMode <> wmRead then Exit;
  if Assigned(OnDownloadComplete) and (FPercentDone >= 100) then
    OnDownloadComplete(Self)
  else if Assigned(OnDownloadCancel) then
    OnDownloadCancel(Self);
end;

function TDownloader.EXDownload(AURL, ADestFile: String;
  AAutoDisconnect: Boolean): Boolean;
var
  fsBuffer: TFileStream;
  idHttp: TIdHttp;
begin
  if FileExists(ADestFile) then
    fsBuffer := TFileStream.Create(ADestFile, fmOpenReadWrite)
  else
    fsBuffer := TFileStream.Create(ADestFile, fmCreate);

  fsBuffer.Seek(0, soFromEnd);
  try
    idHttp := TIdHttp.Create(nil);
    idHttp.OnWorkBegin := idHttpWorkBegin;
    idHttp.OnWork := idHttpWork;
    idHttp.OnWorkEnd := idHttpWorkEnd;
    idHttp.Request.CacheControl := 'no-store';
    try
      ...
      idHttp.Get(AURL, fsBuffer);
      ...
    finally
      idHttp.Free;
    end;
  finally
    fsBuffer.Free;
  end;
end;

……

procedure TDownloader.Execute;
begin
  Inherited;
  while not Terminated do
  begin
    if FUrl <> '' then
    begin
      EXDownload(FUrl, FFilename, True);
    end;
  end;
end;

... 关于主窗体进度

procedure TfrmDownloadList.DownloadProgress(Sender: TObject; aPercent:Integer;
    aProgress, aProgressMax, aBytesPerSec: Int64);
var
  yts: PYoutubeSearchInfo;
begin
  if Assigned(FCurrentDownload) then
  begin
    yts := vstList.GetNodeData(FCurrentDownload);
    yts.Tag := aPercent;
    ProgressBar.Position := aPercent;
    vstList.InvalidateNode(FCurrentDownload);
    StatusBar.Panels.Items[1].Text := 'Download: ' + FormatByteSize(aProgress) + '/' +
      FormatByteSize(aProgressMax);
    StatusBar.Panels.Items[2].Text := 'Speed: ' + FormatByteSize(aBytesPerSec) + 'ps';
    Application.ProcessMessages;
  end;
end;

只有当网络由于信号不佳而下降时,我才没有问题。 这是我的应用程序lookslike

【问题讨论】:

  • 对包含“冻结”一词的问题的答案通常是:使用线程。如果“TDownloader”已经是一个线程(这里不明显),那么您发布的代码并不是您的应用程序冻结的原因。也许在主线程中有一些等待调用?
  • TDownloader 是一个踏板,如果我有良好的信号,工作就很好。进度条运行良好,即使下载?> 800mb,我也可以完美地拖动我的窗口。但是当信号断开我的应用程序时,它开始没有响应。
  • 如何更新进度条?请在问题中包含所有相关代码。
  • 显示 idHttpWorkBegin、idHttpWork 和 idHttpWorkEnd 的代码;

标签: delphi delphi-2009 indy


【解决方案1】:

如果我们假设 TDownloader.OnDownloadProgress 分配给 TfrmDownloadList.DownloadProgress 方法,那么您的问题是您正在从辅助线程(即不是从主线程)调用 VCL 代码(您更新进度条) .不支持。

您需要使用线程内的 Synchronize 语句来包装调用。 Synchronize 在主线程上调用无参数方法。因此,您需要存储所需的变量,然后对 TDownloader 类中的方法调用 Synchronize,然后调用 TfrmDownloadList.DownloadProgress

您不能直接或间接从在主线程以外的另一个线程上运行的代码中调用 TfrmDownloadList.DownloadProgress,因为它会更新 VCL 对象,而且 VCL 不是线程安全的。

您的 DownloadComplete 事件也是如此,如果它更新任何 VCL 对象...

【讨论】:

  • 我明白你的意思。但是 TDownloader 是基于 Tthread 的类,因此没有直接调用主窗体。我所做的是 FDownloader := TDownloader.Create;并为其分配事件。顺便说一句,TfrmDownloadList 是我的 Main 表单。
  • 主窗体是直接使用还是通过事件使用都没有关系。关键是您从辅助线程设置 VCL 控件的属性 - 这是不允许的。您需要在主线程的上下文中调用该事件 - 使用 PostMessageSynchronize
  • @XBasic3000:您的 OnWork 事件处理程序在使用 TIdHTTP 的工作线程的上下文中调用。然后他们在同一个工作线程的上下文中调用您自己的状态事件处理程序。然后,您的状态处理程序会在同一个工作线程的上下文中更新 UI,而不是在主 UI 线程的上下文中。
  • @jpfollenius,是的,我认为你是对的。我试图改变我的实现。谢谢你的想法。
【解决方案2】:

你用 TIdAntiFreeze 怎么样?

TIdAntiFreeze 实现了一个 GUI 集成类,可确保 处理器时间分配给应用程序主线程。

Indy 使用阻塞套接字模型。调用中的方法 Indy 组件在完成之前不会返回。如果来电 在主线程中进行,这将导致应用程序用户 在 Indy 通话期间“冻结”的界面。 TIdAntiFreeze 中和 这种效果。

TIdAntiFreeze 允许 Indy 处理应用程序消息,以便 Windows 消息在 Indy 阻塞套接字时继续执行 通话生效。

一个应用程序中只能有一个 TIdAntiFreeze 处于活动状态。

【讨论】:

  • OP已经在使用线程,所以不需要TIdAntiFreeze
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-02
  • 1970-01-01
  • 1970-01-01
  • 2021-09-09
  • 1970-01-01
相关资源
最近更新 更多