Emily
Emily

Reputation: 539

How to use IdHTTPWork in secondary thread to update progressbar by summing downloaded data

I'm developing a multithread download application. I have one thread that creates many threads that download data. While downloading I need to see the progress in progress bar, so I set the maximum as the size of the file, and I calculate current downloaded data by using IdHTTPWork, which I added as a procedure of thread (secondary thread). When my app is started, the main thread creates other threads to download (in the loop for) and set the position of begin and end (idhttp.request.range), then each thread starts downloading like this:

 HTTP.Request.Range := Format('%d-%d',[begin ,end]);
 HTTP.Get(url,fs);

this is the procedure of secondarythread.work:

 procedure TSecondaryThread.IdHTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount:      Int64);
 begin

  if AWorkMode = wmRead then
    position:= AWorkCount;// position is a global variable
    SendMessage(HWND_BROADCAST,MyMessage, 2,position);
 end;

I don't know if this is the right code, but I can't find another solution. Each thread can increment position using the value of downloaded data, so position will contain the global downloads in instant S, I don't know if this is true. Now my questions: 1- the progress doesn't correspond to the current amount of downloaded data; instead, it increments very slowly. 2-when I add -just when I add- Asend message in this procedure, it never stops working!! So what is the problem?

Upvotes: 0

Views: 601

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 598134

You have the right idea by giving each worker thread its own TIdHTTP object and its own OnWork event handler. But you are not delivering those status updates to the main thread correctly.

Use PostMessage() instead of SendMessage() so that you do not slow down your worker threads.

You have multiple worker threads posting status updates to the main thread, so DO NOT use a global variable to hold the progress, and certainly DO NOT have the worker threads update that variable directly. Each worker thread should put its current status directly in the parameters of the message that gets posted to the main thread, and then the main thread can have a private counter variable that it increments with each status update.

DO NOT post the status updates using HWND_BROADCAST - that broadcasts the message to every top-level window in the system! Post the messages only to your main thread, by posting to an HWND that belongs to the main thread (I would suggest using AllocateHWnd() for that).

Try something like this:

unit StatusUpdates;

uses
  Windows;

interface

type
  PStatus = ^TStatus;
  TStatus = record
    BytesDownloadedThisTime: Int64;
    BytesDownloadedSoFar: Int64;
    MaxBytesBeingDownloaded: Int64;
 end;

var
  StatusUpdateWnd: HWND = 0;

implementation

end.

uses
  ..., StatusUpdates;

type
  TMainForm = class(TForm)
    ...
  private
    TotalDownloaded: Int64;
    ...
  end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  StatusUpdateWnd := AllocateHWnd(StatusWndProc);
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  if StatusUpdateWnd <> 0 then
  begin
    DeallocateHWnd(StatusUpdateWnd);
    StatusUpdateWnd := 0;
  end;
end;

procedure TMainForm.StartDownload;
begin
  ProgressBar1.Position := 0;
  ProgressBar1.Max := FileSizeToBeDownloaded;
  TotalDownloaded := 0;
  // create download threads...
end;

procedure TMainForm.StatusWndProc(var Message: TMessage);
var
  Status: PStatus;
begin
  if Message.Msg = MyMessage then
  begin
    Status := PStatus(Message.LParam);
    try
      if Status.BytesDownloadedThisTime > 0 then
      begin
        Inc(TotalDownloaded, Status.BytesDownloadedThisTime);
        ProgressBar1.Position := TotalDownloaded;
      end;
      // use Status for other things as needed...
    finally
      Dispose(Status);
    end;
  end else
    Message.Result := DefWindowProc(StatusUpdateWnd, Message.Msg, Message.WParam, Message.LParam);
end;

uses
  ..., StatusUpdates;

type
  TSecondaryThread = class(TThread)
  private
    FTotalBytes: Int64;
    FMaxBytes: Int64;
    procedure PostStatus(BytesThisTime: Int64);
    ...
 end;

procedure TSecondaryThread.PostStatus(BytesThisTime: Int64);
var
  Status: PStatus;
begin
  New(Status);
  Status.BytesDownloadedThisTime := BytesThisTime;
  Status.BytesDownloadedSoFar := FTotalBytes;
  Status.MaxBytesBeingDownloaded := FMaxBytes;
  if not PostMessage(StatusUpdateWnd, MyMessage, 2, LPARAM(Status)) then
    Dispose(Status);
end;

procedure TSecondaryThread.IdHTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
  if AWorkMode = wmRead then
  begin
    FTotalBytes := 0;
    FMaxBytes := AWorkCountMax;
    PostStatus(0);
  end;
end;

procedure TSecondaryThread.IdHTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
var
  BytesThisTime: Int64;
begin
  if AWorkMode = wmRead then
  begin
    BytesThisTime := AWorkCount - FTotalBytes;
    FTotalBytes := AWorkCount;
    PostStatus(BytesThisTime);
  end;
end;

Upvotes: 7

Related Questions