Synchonize thread with form

Hi,

I've implemented a similar setup based on following article from Geert Gruwez in my application.
http://www.experts-exchange.com/articles/239/Displaying-progress-in-the-main-form-from-a-thread-in-Delphi.html

In my application I send an E-mail inside this thread. The progress of the actual sending is then shown inside a progressbar. For this I use clSMTP.OnProgress event (Clever components suite).

When this event is placed on the mainform itself, it shows a fluid progress of the sending. But when placed inside the thread I only get portions of it on the mainform. In the end it doesn't even fill the progressbar to max.
procedure TMyThread.clSMTPProgress(Sender: TObject; ABytesProceed, ATotalBytes: Int64);
begin
  // Mail sending is started. Progress of bytes send is shown on the main form.
  Progress(ABytesProceed, ATotalBytes, vSendingIsStarted, vSendingIsComplete);
end;

Open in new window

procedure TProgressThread.Progress(aProgress, aTotaal: Integer; aSendingIsStarted, aSendingIsComplete: Boolean);
begin
  FSendingIsStarted  := aSendingIsStarted;
  FSendingIsComplete := aSendingIsComplete;
  FProgressTotaal    := aTotaal;
  FProgressValue     := aProgress;
  Synchronize(SynchedProgress);
end;

Open in new window

procedure TMainForm.UpdateProgressBar(aProgress, aTotaal: Integer; aSendingIsStarted, aSendingIsComplete: Boolean);
begin
  pbVoortgangVerzenden.Properties.Max    := aTotaal;
  pbVoortgangVerzenden.Position          := aProgress;
  pbVoortgangVerzenden.Update;       // repaint
end;

Open in new window

I guess this has to do with the synchronization. Is there a way to smooth this out?
Stef MerlijnDeveloperAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Sinisa VukSoftware architectCommented:
May clSMTP works differently when is in own tread. How SynchedProgress looks like?
it is good to set first and last Sync in tread procedure:
procedure TProgressThread.Execute;
begin
   //at start
   Progress(0, 0, True, False);
   //send mail here....
   //at end... if mail send started ....
   Progress(100, 100, True, True); 
end;

Open in new window

0
Stef MerlijnDeveloperAuthor Commented:
That is exactly how it is setup.
// Start
Progress(0, 100, False, False);        // Set initial startingpoint for progressbar
** some code here                          // Compose E-mail (BuildMessage)
Progress(0, 100, True, False);         // Initiate start of actual sending
clSMTP.Send(clMailMessage);       // Send E-mail - Actual
Progress(100, 100, True, False);      // Workaround to fill progressbar on mainform.
// End
Progress(100, 100, True, True);      // After all processing in thread is completed

Open in new window

0
Sinisa VukSoftware architectCommented:
You didn't post SynchedProgress procedure.
Second, try to put Sleep(1000) before last line in Threads Execute procedure..... Did you noticed any change?
0
Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

Stef MerlijnDeveloperAuthor Commented:
Adding Sleep(1000) makes no difference.
procedure TProgressThread.SynchedProgress;
begin
  if Assigned(FProgressProc) then
    FProgressProc(FProgressValue, FProgressTotaal, FSendingIsStarted, FSendingIsComplete);
end;

Open in new window

0
Geert GOracle dbaCommented:
you might be getting an error ?

using a try finally would make certain you have the last message of 100%

/ Start
Progress(0, 100, False, False);        // Set initial startingpoint for progressbar
** some code here                          // Compose E-mail (BuildMessage)
Progress(0, 100, True, False);         // Initiate start of actual sending
try
  clSMTP.Send(clMailMessage);       // Send E-mail - Actual
  Progress(100, 100, True, False);      // Workaround to fill progressbar on mainform.
finally
  // End
  Progress(100, 100, True, True);      // After all processing in thread is completed
end;

Open in new window


threading can be very nasty, if it's not 100% correct, it's difficult to find the error
0
Stef MerlijnDeveloperAuthor Commented:
Yes that would be a workaround to get the 100%, but it doesn't make the progress in the progressbar visually fluid. I still get chunks of progress-steps.
I've asked Clever Components if they have a solution. They seem to have some TclThreadSynchronizer class for synchronizing the OnProgress calls. I hope that will shine some light on the subject.
0
Geert GOracle dbaCommented:
Fluid ?
There is no point in using my sample to make it fluid.
That sample is for step feedback.

The only way you can get it fluid, is when you work time based
Do you know how long the process takes ?
Cut the time up in 1/100 th of the total time and then step the progress value 1 to a max of 100.
Assume your process takes 60 secs ... add 1 to the position of the progress bar every 600ms

Fluid and progress steps having nothing to do with each other.
0
Stef MerlijnDeveloperAuthor Commented:
No, the E-mail can be of any size (attachments and such), also customers have different uploadspeed. Therefore there is no way I can calculate the time.
I do know the size of the whole E-mail that must be send.
procedure TMyThread.clSMTPProgress(Sender: TObject; ABytesProceed, ATotalBytes: Int64);
begin
  // Mail sending is started. Progress of bytes send is shown on the main form.
  Progress(ABytesProceed, ATotalBytes, vSendingIsStarted, vSendingIsComplete);
end;

Open in new window

0
Geert GOracle dbaCommented:
some apps activate marquee for the progressbar, instead of indicating progress
just a busy indicator ... and way easier
it's like watching Knight Rider ... fascinating
0
Stef MerlijnDeveloperAuthor Commented:
That would be a last stroh to get passed this issue :-)
I'm waiting for an answer from Clever Components. Maybe they can come up with some solution.
0
Geert GOracle dbaCommented:
not really sure what you are looking for ...
you'll have a somewhat fluid flow of a progress bar if you have enough steps in the progress reporting

this sample takes about a minute to calculate prime numbers if the range starts at 1000000 and ends at 1200000
it doesn't use the synchronize method.
Only windows messages to report and send back the data

I thought i'd write a different approach to threading :)

unit uMT1Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Spin, ComCtrls;

type
  TfrmMT1Main = class(TForm)
    lbPrimes: TListBox;
    seRangeStart: TSpinEdit;
    seRangeEnd: TSpinEdit;
    lblRangeStart: TLabel;
    lblRangeEnd: TLabel;
    btnCalculate: TButton;
    lblPrimes: TLabel;
    memInfo: TMemo;
    lblInfo: TLabel;
    pbMain: TProgressBar;
    procedure btnCalculateClick(Sender: TObject);
  private
    procedure WMUser(var Msg: TMessage); message WM_USER;
  end;

var
  frmMT1Main: TfrmMT1Main;

implementation

uses uMTThreads;

{$R *.dfm}

procedure TfrmMT1Main.btnCalculateClick(Sender: TObject);
begin
  memInfo.Clear;
  // Validate
  if (seRangeStart.Value > 0) and (seRangeStart.Value <= seRangeEnd.Value)
    and (seRangeEnd.Value >= 1) then
  begin
    // Start time
    memInfo.Lines.Add('Prime numbers start:  ' + FormatDateTime('yyyy/mm/dd hh:nn:ss', Now));
    // List primes
    CalcPrimes(seRangeStart.Value, seRangeEnd.Value, Handle, WM_USER);
    // End time
    memInfo.Lines.Add('Prime numbers done :  ' + FormatDateTime('yyyy/mm/dd hh:nn:ss', Now));
  end;
end;

procedure TfrmMT1Main.WMUser(var Msg: TMessage);
begin
  if Msg.Msg = WM_USER then
  begin
    if (Msg.WParam = 1) and (Msg.LParam >= 0) and (Msg.LParam <= 100) then
      pbMain.Position := Msg.LParam
    else if (Msg.WPARAM = 2) and (Msg.LParam > 0) then
    begin
      lbPrimes.Items.BeginUpdate;
      try
        lbPrimes.Items.Assign(TStrings(Msg.LParam));
      finally
        lbPrimes.Items.EndUpdate;
      end;
    end;
  end;
end;

end.

Open in new window


unit uMtThreads;

interface

uses Classes;

procedure CalcPrimes(RangeStart, RangeEnd: Integer; FormHandle: THandle; Msg: Cardinal);

implementation

uses Windows, Messages, SysUtils;

type
  TThreadCalcPrimes = class(TThread)
  protected
    fRangeStart: Integer;
    fRangeEnd: Integer;
    fFormHandle: THandle;
    fMsg: Cardinal;
    procedure Execute; override;
  public
    constructor Create(aRangeStart, aRangeEnd: Integer; aFormHandle: THandle; aMsg: Cardinal);
  end;

procedure CalcPrimes(RangeStart, RangeEnd: Integer; FormHandle: THandle; Msg: Cardinal);
begin
  TThreadCalcPrimes.Create(RangeStart, RangeEnd, FormHandle, Msg);
end;

{ TThreadCalcPrimes }

constructor TThreadCalcPrimes.Create(aRangeStart, aRangeEnd: Integer;
  aFormHandle: THandle; aMsg: Cardinal);
begin
  inherited Create(False);
  FreeOnTerminate := True;
  fRangeStart := aRangeStart;
  fRangeEnd := aRangeEnd;
  fFormHandle := aFormHandle;
  fMsg := aMsg;
end;

procedure TThreadCalcPrimes.Execute;
var List: TStrings;
  I, J, delta, Progress: Integer;
  IsPrime: Boolean;
begin
  List := TStringList.Create;
  try
    // Started the thread: progress = 0%
    Progress := 0;
    PostMessage(fFormHandle, fMsg, 1, Progress);

    // Calculate 1% step
    delta := Trunc((fRangeEnd - fRangeStart) / 100);

    // Find the primes
    for I := fRangeStart to fRangeEnd do
    begin
      // exit when thread is terminated
      if Terminated then Exit;

      IsPrime := True;
      // not a prime when divisable by 2 to n-1
      for J := 2 to I-1 do
        if (I > 3) and (I mod J = 0) then
        begin
          // If not a prime, stop the loop
          // we only need to find 1 value to identify if this is a prime
          IsPrime := False;
          Break;
        end;
      // Add the prime to the list
      if IsPrime then
        List.Add(IntToStr(I));
      // Return feedback
      if (delta > 1) and ((I - fRangeStart) mod delta = 0) then
      begin
        Inc(Progress);
        PostMessage(fFormHandle, fMsg, 1, Progress);
      end;
    end;
    // Return progress = 100%
    PostMessage(fFormHandle, fMsg, 1, 100);

    // Send the data to the form
    SendMessage(fFormHandle, fMsg, 2, Integer(List));

  finally
    List.Free;
  end;
end;


end.

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Stef MerlijnDeveloperAuthor Commented:
I've decided to go for the Windows messages solution.
Thank you very much.
0
Geert GOracle dbaCommented:
I'm writing an article on different techniques to accomplish ways for multi-threading
I'll provide a link when finished.
0
Stef MerlijnDeveloperAuthor Commented:
Great, thank you very much Geert.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.