Wait for Thread to finish to proceed with the code - Delphi

2

I'm facing a problem in an application I've developed. It is an automatic updater that basically downloads the necessary files and extracts them in a suitable way. The problem is time to download. I put the download method inside a Thread, put it inside the Syncronize, however it "hangs", does not let me move the window (interface) until the process finishes. Remember that when downloading it calls the method that changes the progress bar.

In another case, I only put the method that changes the loading bar inside the syncronize, causing the window to not be locked anymore, I can move it while the download is done, however the rest of the code is and this can not happen in my case because I depend on the file being downloaded to continue.

The final question is, how do I wait for this download to finish, does this thread complete before proceeding with the code execution? Because if I continue it will give an error in the application because it does not have the file, because the file is still being downloaded.

Calling the download method inside the main form (uFrmPrincipal.pas)

ThreadBaixarAtualização('c:\caminho\atua_beta',
  'http://www.site.com.br/atualizacao/atua_beta.zip', true);

ThreadBaixarAtualização('c:\caminho\scripts',
    'http://www.site.com.br/atualizacao/scripts.zip', False);

Method to create the Thread instance inside the main Form (uFrmPrincipal.pas)

procedure TfrmAtualizador.ThreadBaixarAtualização(CaminhoNomeArquivo,
    URL: String; Status: Boolean);
  begin
    FConHTTPThread := TConHTTPThread.create(true, UpdateProgressBar);
    FConHTTPThread.FreeOnTerminate := true;
    FConHTTPThread.URL := URL;
    FConHTTPThread.CaminhoNomeArquivo := CaminhoNomeArquivo;
    FConHTTPThread.Status := Status;
    FConHTTPThread.Start;
  end;

Method that changes the progress bar on the main form (uFrmPrincipal.pas)

  procedure TfrmAtualizador.UpdateProgressBar(aProgress, Total: Int64);
  begin
    gProgresso.MaxValue := Total;
    gProgresso.Progress := aProgress;
    gProgresso.Update;
  end;

Class to download the files - Separate Unit (uConHTTPThread.pas)

  unit uConHTTPThread;

  interface

  uses
    System.Classes, SysUtils, Forms, IdHTTP, IdComponent,
    Vcl.Dialogs, System.UITypes;

  type
    // Tipo que contem um método com os mesmos parâmetros que a barra de progresso
    // do formulario principal
    TBarraProgress = procedure(aProgress, Total: Int64) of object;

    TConHTTPThread = class(TThread)
      procedure IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
        AWorkCount: Int64);
      procedure IdHTTP1WorkBegin(ASender: TObject; AWorkMode: TWorkMode;
        AWorkCountMax: Int64);
      procedure IdHTTP1WorkEnd(ASender: TObject; AWorkMode: TWorkMode);
    private
      FIdHTTP1: TIdHTTP;
      FValorMaximo: Int64;
      FProgresso: Int64;
      FfileDownload: TFileStream;
      FCaminhoNomeArquivo, FURL: String;
      FStatus: Boolean;
      FBarraProgress: TBarraProgress;
      procedure SetCaminhoNomeArquivo(const Value: String);
      procedure SetURL(const Value: String);
      procedure SetStatus(const Value: Boolean);
      procedure AtualizaBarra;
    protected
      procedure Execute; override;
      procedure BaixarAtualizacao;
      procedure Progress(aProgress: Int64); virtual;
    Public
      property Progresso: Int64 read FProgresso;
      property Total: Int64 read FValorMaximo;
      property CaminhoNomeArquivo: String read FCaminhoNomeArquivo
        write SetCaminhoNomeArquivo;
      property URL: String read FURL write SetURL;
      property Status: Boolean read FStatus write SetStatus;

      constructor Create(CreateSuspended: Boolean;
        pBarraProgress: TBarraProgress); reintroduce; virtual;
    end;

  implementation

  // Responsável por efetuar o Donwload dos arquivos de atualização
  procedure TConHTTPThread.BaixarAtualizacao;
  begin
    try
      if not Assigned(FIdHTTP1) then
        FIdHTTP1 := TIdHTTP.Create(Nil);

      FfileDownload := TFileStream.Create(FCaminhoNomeArquivo +
        ExtractFileExt(FURL), fmCreate);

      if Status = True then
      begin
        with FIdHTTP1 do
        begin
          OnWork := IdHTTP1Work;
          OnWorkBegin := IdHTTP1WorkBegin;
        end;
      end;
      FIdHTTP1.Get(FURL, FfileDownload);
    finally
      if Assigned(FIdHTTP1) then
        FreeAndNil(FIdHTTP1);
      if Assigned(FfileDownload) then
        FreeAndNil(FfileDownload);
    end;
  end;

  constructor TConHTTPThread.Create(CreateSuspended: Boolean;
    pBarraProgress: TBarraProgress);
  begin
    inherited Create(CreateSuspended);
    FBarraProgress := pBarraProgress;
  end;

  procedure TConHTTPThread.Execute;
  begin
    if (not Terminated) then
    begin
      Sleep(10);
      Synchronize(BaixarAtualizacao);
    end;
  end;

  procedure TConHTTPThread.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
    AWorkCount: Int64);
  begin
    Progress(AWorkCount);
  end;

  procedure TConHTTPThread.IdHTTP1WorkBegin(ASender: TObject;
    AWorkMode: TWorkMode; AWorkCountMax: Int64);
  begin
    // Verifica tamanho total do arquivo a ser baixado
    FValorMaximo := AWorkCountMax;
  end;

  procedure TConHTTPThread.IdHTTP1WorkEnd(ASender: TObject; AWorkMode: TWorkMode);
  begin
    FValorMaximo := FValorMaximo;
  end;

  procedure TConHTTPThread.Progress(aProgress: Int64);
  begin
    FProgresso := aProgress;
    AtualizaBarra;
  end;

  procedure TConHTTPThread.SetCaminhoNomeArquivo(const Value: String);
  begin
    FCaminhoNomeArquivo := Value;
  end;

  procedure TConHTTPThread.SetStatus(const Value: Boolean);
  begin
    FStatus := Value;
  end;

  procedure TConHTTPThread.SetURL(const Value: String);
  begin
    FURL := Value;
  end;

  procedure TConHTTPThread.AtualizaBarra;
  begin
    // Atualiza barra de progresso no formulário principal
    if Assigned(FBarraProgress) then
      FBarraProgress(FProgresso, FValorMaximo);
  end;

  end.
    
asked by anonymous 18.04.2018 / 16:03

3 answers

1

If you use the Synchronize there inside the Execute of the thread, who will perform the work of executing the code block "DownloadUpdate" will be the main thread, ie in this case you kind of "killed" the use of the thread and made your program run the download process without it.

For your convenience, you can implement a method that executes at the end of the thread, and arrow it in the OnTerminate of the thread when it is created, it follows a simple example that I made, when the thread finishes, will execute the block of code that is inside the EndThread.

TTeste = class(TThread)
public
    procedure Execute;
    procedure FinalizarThread(Sender: TObject);

    constructor Create;
end;

implementation 

{$R *.dfm}

{ TTeste }

constructor TTeste.Create;
begin
    OnTerminate :=FinalizarThread;
end

procedure TTeste.Execute;
begin
    ////Executa o processo de download
end;

procedure TTeste.FinalizarThread(Sender: TObject);
begin
    //Processo a realizar após finalizar a thread
end;
    
24.04.2018 / 05:26
1

The right thing to do is to use Syncronize just to update the graphical interface (not to download). I've done something similar using the Thread's OnTerminate event: In a variable it stored the total amount of threads and in the OnTerminate handler it decremented the variable. When it reached zero, processing continued.

    
19.04.2018 / 16:23
0

Just like you have a procedure called IdHTTP1WorkBegin , you can develop a IdHTTP1WorkEnd , and in IdHTTP1WorkBegin you can put some functionality (Controls as buttons and etc) with Enabled = False and IdHTTP1WorkEnd % re-enable these controls disabled.

The purpose of the thread is just not to need the MainThread to wait for the completion of the secondary process, so what can be done is an auxiliary control like the one suggested above.

    
18.04.2018 / 16:53