Delphi Updating TMemo Box and application.processmessages

Hey,

My multithreaded application has a GUI with a log screen (TMemo box). I send messages back to the main GUI using PostMessage from the threads to update the log:

This code is incomplete.. just showing you an example of the 0 case.
 a := Tstringlist.create;
 GetMem(s,255);
 if globalgetatomname(Message.LParam,s,255) > 0 then sdata := strpas(s);
 case Message.WParam of
 0: begin//write log
 log.log.Lines.Add(sdata);

Open in new window


Now on the TMemo box "onchange" event I put Application.processmessages; Without this the GUI is frozen the entire time the log is being written too..which is frequent. However if the log is written to, to fast the application crashes. With application.processmessages removed it never crashes.

My question is, is there a proper way to update the GUI/Tmemo boxes and what not without using application.processmessages? Should I be using BeginThread to write to the log?

Please help
nickdelphi777Asked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
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.

Marco GasiFreelancerCommented:
In my multithreaded app I use a different tecnique. I update the control from within the thread, using Synchronize method
unit SampleThreads;

interface

uses SysUtils, Classes, StdCtrls, ComCtrls, Messages;

type
  TSampleThread = class(TThread)
    FProcMemo: TMemo;
    S: string;
  private
    procedure UpdateMemo;
  protected
    procedure Execute; override;
  public
    constructor Create(AProcMemo: TMemo);
    destructor Destroy; override;
  end;

implementation

//{$DEFINE DEBUGGING}

uses Windows, Forms, Dialogs, UMain,
  {$IFNDEF COMPILER6_UP}
  FileCtrl,
  {$ENDIF};


constructor TBackupThread.Create(AProcMemo: TMemo);
begin
  FProcMemo := AProcMemo;
  inherited Create(False);
end;

destructor TBackupThread.Destroy;
begin
  inherited Destroy;
end;

procedure TBackupThread.Execute;
begin
  for i := 0 to 9999 do
  begin
    S := 'Processing data '+ IntToStr(i);
    Synchronize(UpdateMemo);
    Application.ProcessMessages;
    if Terminated then
      Exit;
  end;
  if Terminated then
    Exit;
end;

procedure TBackupThread.UpdateMemo;
begin
  FProcMemo.Lines.Add('Processing ' + s);
end;

end.

Open in new window


Creating the thread I pass a reference to my memo and within the threa I use that reference to update its content.
0
jimyXCommented:
You invited that crash the moment you gave access to the TMemo, which is on the Main thread, to another thread to write on.

Some Thread techniques would help to make it possible, to share data between threads, by using Synchronize, or CriticalSection to lock resources while writing to.

  TMyThread = class(TThread)
  private
    msg: String;
    procedure WriteMemo;
  protected
    procedure Execute; override;
  public
    Constructor ...
    destructor ...
  end;

procedure TMyThread.WriteMemo;
begin
  log.log.Lines.Add(msg);
end;

procedure TMyThread.Execute;
begin
  a := Tstringlist.create;
  try
    try
      GetMem(s,255);
      if globalgetatomname(Message.LParam,s,255) > 0 then sdata := strpas(s);
      case Message.WParam of
      0: begin//write log
      msg:= sdata;
      Synchronize(WriteMemo);
    Except
      ... //handle errors
    end;
  finally
    a.free;
  end;
end;

Open in new window

0
nickdelphi777Author Commented:
So I would pass my log on tform1 in the create constructor of the thread and with your code the memo on tform1 will actually be updated?
0
OWASP: Threats Fundamentals

Learn the top ten threats that are present in modern web-application development and how to protect your business from them.

nickdelphi777Author Commented:
Jimyx, I actually have that message receiving code in the GUI under Tform1..I should of mentioned that sorry.
0
Marco GasiFreelancerCommented:
If you're speaking to me, no, my code si just an example.
0
jimyXCommented:
>   I actually have that message receiving code in the GUI under Tform1..I should of mentioned that sorry.

This changes the code but the concept remains, if you access shared data across threads:
  TMyThread = class(TThread)
  private
    msg: String;
    procedure WriteMemo;
  protected
    procedure Execute; override;
  public
    Constructor ...
    destructor ...
  end;

procedure TMyThread.WriteMemo;
begin
  log.log.Lines.Add(msg);
end;

procedure TMyThread.Execute;
begin
  ...
  try
    try
      ...
      msg:= some data;
      Synchronize(WriteMemo);
    Except
      ... //handle errors
    end;
  finally
    ... //free objects
  end;
end;

Open in new window

0
jimyXCommented:
>   Now on the TMemo box "onchange" event I put Application.processmessages; Without this the GUI is frozen

Threads are solving this freezing issue, without the need to use "Application.processmessages".

How do you pass and process the data between your threads?
Would you elaborate please?
0
nickdelphi777Author Commented:
I don't send messages between threads.. the threads have a update function that sends to the MAIN form to update counters, and the log.

my update code:
procedure TBaseThread.sendupdate(head:integer;sdata:string);
var
s:dword;
begin
s := GlobalAddAtom(PChar(sdata));
win32check(PostMessage(form1.handle,th_message,head,s));
end;

Open in new window

0
nickdelphi777Author Commented:
I'm assuming if I have the threads write to the log on tform1.. using syncronize i will be by passing my current issue with application.processmessages;
0
jimyXCommented:
Did you try BeginUpdate or repaint?
That's another way to avoid "Application.processmessages":

 a := Tstringlist.create;
 GetMem(s,255);
 if globalgetatomname(Message.LParam,s,255) > 0 then sdata := strpas(s);
 case Message.WParam of
 0: begin//write log
 log.log.Lines.BeginUpdate;
 log.log.Lines.Add(sdata);
 log.log.Lines.EndUpdate;

Open in new window


Although you could have handled that differently.
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
jimyXCommented:
> I'm assuming if I have the threads write to the log on tform1.. using syncronize i will be by passing my current issue with application.processmessages;

Absolutely. If that's your only issue.
0
nickdelphi777Author Commented:
Yes thats my only issue.. i will try them in the next 20 minutes and get back to you thanks!
0
Bruno BuesserCommented:
With Synchronize you do not really decouple the threads and the main thread. Synchronize uses SendMessage to synchronize threads. That means that the thread is blocked while the main thread is writing to the Memo. The thread does not proceed before SendMessage has returned. This repetitive delay may cause problems if your threads have time critical or many things to do. The writing to the GUI slows down your whole application.
I normaly prefer to use Critical Sections in such cases.

1. Create a critical section in the main forms FormCreate before you start any threads.
2. Create a TStringList in FormCreate which you use as string buffer
2. Write a Log procedure in the main thread and store the log strings into the buffer.

procedure MainForm.Log(AMessage: string);
      CriticalSection.Enter;
      try
         Buffer.Add(AMessage)
      finally
        CriticalSection.Leave;
      end;

Open in new window



3. Use a TTimer in the MainForm to copy the buffer's entry to the memo lines

  Memo.Lines.BeginUpdate;
  CriticalSection.Enter
  try
    Memo.Lines.AddStrings(Buffer);
    Buffer.Clear;
  finally
    CriticialSection.Leave;
    Memo.Lines.EndUpdate;  // >> after Leave so that other threads are delayed as short as possible
  end;

Open in new window


That's only a simple example to show the idea. To speed up you could use an array of strings and allocate space in advance and handle it as a fifo with a Head and Tail pointer.
0
Geert GOracle dbaCommented:
multithreading and what not to do ...
>> Marco Gasi, you're winning !

1: don't update visual components from within a thread !
even a TMEMO
drawing on screen should be done by the mainthread
your sample http://#a40721415 is a real thing of how not to do it !
Synchronize within a thread will pause that thread until the main thread is finished drawing and will then resume
You didn't even use beginupdate and endupdate on the Lines !

The sample works, I agree, but there is no more multithreading going on.
The threads are given time to work 1 by 1. The mainthread and the other threads are never actually working simultanteously !
Bruno,  a bit further down also states this

>> nick
in your sample ... http://#a40721511 you have this line :
win32check(PostMessage(form1.handle,th_message,head,s));
Is the thread in the main form or did you add the main unit to your thread unit ?
For the former  ... you'll have to change some code to separate the thread into a separate unit if you don't want circular unit reference
For the latter ... you have circular reference to the main unit in your thread unit

the hardest part with multithreaded apps : giving feedback !
the simple question you need to ask yourself: do you really need to give feedback ?

i have found threads working so much faster if feedback is not done.
the only time when i give feedback is:
> when the thread is completely finished
> when an error is thrown
0
Geert GOracle dbaCommented:
add this line to bruno's at the end:

 Memo.Lines.BeginUpdate;
  // skipped lines
  end;
 Memo.Update;
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.