XBasic3000
XBasic3000

Reputation: 3486

TIdHttp freezes when the internet gets slower

How to avoid freezing the idHTTP when the internet become slower or no connectivity. My application get freeze and I could not even close the form.

This is how I setup my code

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;

... on the main form progress

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;

I don't have problem when the internet is good only when it drops due to poor signal. this is my app lookslike

Upvotes: 0

Views: 1120

Answers (2)

Zam
Zam

Reputation: 2940

How about you using TIdAntiFreeze ?

TIdAntiFreeze implements a GUI-integration class that ensures processor time is allocated for the Application main thread.

Indy works on the blocking sockets model. Calls made to methods in the Indy components do not return until they are complete. If calls are made in the main thread, this will cause the Application User Interface to "freeze" during Indy calls. TIdAntiFreeze counteracts this effect.

TIdAntiFreeze allows Indy to process Application messages so that Windows messages continue to be executed while Indy blocking socket calls are in effect.

Only one TIdAntiFreeze can be active in an application.

Upvotes: 0

HeartWare
HeartWare

Reputation: 8261

If we assume that TDownloader.OnDownloadProgress is assigned to the TfrmDownloadList.DownloadProgress method, then your problem is that you are calling VCL code (your update of the progress bar) from a secondary thread (ie. not from the Main thread). This is not supported.

You'll need to wrap the call with a Synchronize statement from within your thread. Synchronize calls a parameterless method on the main thread. So you need to store the variables that are needed and then call Synchronize on a method in your TDownloader class that then calls on to TfrmDownloadList.DownloadProgress

You cannot call TfrmDownloadList.DownloadProgress directly or indirectly from within code that runs on another thread than the main thread, as it updates VCL objects, and the VCL is not thread-safe.

The same goes for your DownloadComplete event, if it updates any VCL objects...

Upvotes: 2

Related Questions