Icon View Thread

The following is the text of the current message along with any replies.
Messages 1 to 10 of 23 total
Thread Need Help with Threads -- Trying to update VCL component
Fri, Jul 21 2006 12:41 PMPermanent Link

Sean McCall
Hi,

I've decided to start out with threads to do what I had
hoped would be a simple task. I would like to have some sort
of feedback for the user on my progress splash screens so
that it doesn't seem like the system is locked during large
commits or data transfers that cause long delays between
calls to Application.ProcessMessages. I tried a TTimer and
TThread.Synchronize but these are both dependant on the
Application.ProcessMessages firing in the main thread.
TRTLCriticalSection seemed like the ticket. However, when I
make a change the Thread itself seems to be blocked by the
main thread and even this method doesn't resume execution
unless I use Application.ProcessMessages! Any explanation as
to why this is happening and any idea how can I get a second
thread to successfully update a VCL component in the main
thread to without a call to Application.ProcessMessages
would be appreciated. The code I'm using is pretty short, so
I'll append it at the end.

Thanks,

Sean

unit Timer_Unit;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    ProgressBar1: TProgressBar;
    Checkbox1: TCheckBox;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    procedure DoAlarm(ASender: TObject);
  end;
  {
  TSThread - Suspended thread with VCL safe immediate execution
  }
  TSThread = class(TThread)
  public
    constructor Create; reintroduce; virtual;
    procedure VCLSafe(AMethod: TThreadMethod);
  end; {TSThread}
  {
  TSTimerThread - Thread that executes a timed event
  }
  TSTimerThread = class(TSThread)
  private
    { event handlers }
    FOnAlarm: TNotifyEvent;                         {hook
back to event}
  protected
    procedure DoAlarm;
    procedure Execute; override;
  published
    procedure DisableAlarm;
    procedure EnableAlarm;
    { event handlers }
    property OnAlarm: TNotifyEvent read FOnAlarm write
FOnAlarm;
  end; {TSTimerThread}

var
  Form1: TForm1;

implementation


var
  VRTLLock: TRTLCriticalSection;
{critical section}

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  AThread: TSTimerThread;
  AStart: TDateTime;
begin
  AThread := TSTimerThread.Create;
  try
    AThread.OnAlarm := DoAlarm;
    AThread.EnableAlarm;
    AStart := Now;
    repeat
      Caption := FloatToStr(Now);
      if Checkbox1.Checked then begin
        Application.ProcessMessages;
      end; {if process messages}
    until (Now - AStart) * 24 * 60 * 60 > 10;  {10 seconds}
  finally
    FreeAndNil(AThread);
  end; {try-finally}
end; {procedure}

procedure TForm1.DoAlarm(ASender: TObject);
begin
  ProgressBar1.StepIt;
  Refresh;
end;

{
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% TSThread - Thread with VCL Safe Calling %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
}

constructor TSThread.Create;
begin
  inherited Create(True);                           {create
suspended}
end; {procedure}

procedure TSThread.VCLSafe(AMethod: TThreadMethod);
begin
  EnterCriticalSection(VRTLLock);                   {block
main thread}
  try
    AMethod;
{execute method}
  finally
    LeaveCriticalSection(VRTLLock);                 {allow
main thread to continue}
  end; {try-finally}
end; {procedure}

{
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% TSTimerThread - Alarm Timer Thread %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
}

procedure TSTimerThread.Execute;
begin
  while not Terminated do begin                     {not
terminated?}
    try
      Sleep(1000);
      VCLSafe(DoAlarm);                             {do
alarm event}
    except
    end; {try-except}
  end; {while not terminated}
end; {procedure}

procedure TSTimerThread.DisableAlarm;
begin
  Suspended := True;
{suspend the thread}
end; {procedure}

procedure TSTimerThread.EnableAlarm;
begin
  Suspended := False;                               {enable
the thread}
end; {procedure}

procedure TSTimerThread.DoAlarm;
begin
  if Assigned(FOnAlarm) then begin                  {alarm
event?}
    FOnAlarm(Self);                                 {fire
alarm}
  end; {if event handler}
end; {procedure}

initialization
begin
  InitializeCriticalSection(VRTLLock);
{initialize lock object}
end; {initialization}

finalization
begin
  DeleteCriticalSection(VRTLLock);
{release lock object}
end; {finalization}

end.
Fri, Jul 21 2006 3:03 PMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Sean


My approach is to use messages. The basic stuff is in the procedures below. TbgMgr is the background thread. Essentially on each pass through a loop in the thread this gets called, and if appropriate a message is posted. I pass in the handle for the form to receive it when the thread is started, and in that form when the message is received it does everything necessary to update the display.

This ensures that the thread and form (visual components) remain nicely isolated from each other and there's no danger of nasties.


Roy Lambert


procedure TbgMgr.SetProcessed(Processed: integer);
begin
PostErrorCounter := 0;
if (not Terminated) and DoProgress then begin
 inc(ProgressStepsDone);
 if GetEditLock(MNQueue, 1) then begin
  if Processed <= MNQueue.FieldByName('_MaxItems').AsInteger
   then MNQueue.FieldByName('_Processed').AsInteger := Processed
  else MNQueue.FieldByName('_Processed').AsInteger := MNQueue.FieldByName('_MaxItems').AsInteger;
  try
   MNQueue.Post;
   if ProgressStepsDone >= ProgressInterval then begin
    PostMessage(qData.Handle, bgMsgProgress, 0, 0);
    ProgressStepsDone := 0;
   end;
  except
   MNQueue.Cancel;
  end;
 end;
end;
UpdateNewsStats;
end;

procedure ThreadProgress(var Message: TMessage); message bgMsgProgress;


procedure TMainQueue.ThreadProgress(var Message: TMessage);
begin
if (Message.WParam = bgClearThread) and (Message.LParam <> 0) then begin
 udMgr[Message.LParam] := True;
 udThrds[Message.LParam] := nil;
end;
DoRefresh(Self);
end;
Fri, Jul 21 2006 3:48 PMPermanent Link

Sean McCall
Roy,

I appreciate the input, but my understanding is that
PostMessage will add the message to the end of the
application's messaging queue. This means that the message
will not execute until Application.ProcessMessages is called
by either me or the main application thread. What I need is
a way to force a control on a form to update (progress bar,
graphic, caption, or anything) from another thread *without*
the need for the application to process the messages in the
queue. I don't want a long process in the main thread to
make the application appear to be hung.

Thanks,

Sean


Roy Lambert wrote:
> Sean
>
>
> My approach is to use messages. The basic stuff is in the procedures below. TbgMgr is the background thread. Essentially on each pass through a loop in the thread this gets called, and if appropriate a message is posted. I pass in the handle for the form to receive it when the thread is started, and in that form when the message is received it does everything necessary to update the display.
>
> This ensures that the thread and form (visual components) remain nicely isolated from each other and there's no danger of nasties.
>
>
> Roy Lambert
>
>
> procedure TbgMgr.SetProcessed(Processed: integer);
> begin
> PostErrorCounter := 0;
> if (not Terminated) and DoProgress then begin
>   inc(ProgressStepsDone);
>   if GetEditLock(MNQueue, 1) then begin
>    if Processed <= MNQueue.FieldByName('_MaxItems').AsInteger
>     then MNQueue.FieldByName('_Processed').AsInteger := Processed
>    else MNQueue.FieldByName('_Processed').AsInteger := MNQueue.FieldByName('_MaxItems').AsInteger;
>    try
>     MNQueue.Post;
>     if ProgressStepsDone >= ProgressInterval then begin
>      PostMessage(qData.Handle, bgMsgProgress, 0, 0);
>      ProgressStepsDone := 0;
>     end;
>    except
>     MNQueue.Cancel;
>    end;
>   end;
> end;
> UpdateNewsStats;
> end;
>
> procedure ThreadProgress(var Message: TMessage); message bgMsgProgress;
>
>
> procedure TMainQueue.ThreadProgress(var Message: TMessage);
> begin
> if (Message.WParam = bgClearThread) and (Message.LParam <> 0) then begin
>   udMgr[Message.LParam] := True;
>   udThrds[Message.LParam] := nil;
> end;
> DoRefresh(Self);
> end;
Fri, Jul 21 2006 8:21 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Sean,

<< What I need is a way to force a control on a form to update (progress
bar, graphic, caption, or anything) from another thread *without* the need
for the application to process the messages in the queue. I don't want a
long process in the main thread to  make the application appear to be hung.
>>

Check out the TApplication.HookSynchronizeWakeup method and WakeMainThread
variable (Delphi 7).  That should give you what you want.

--
Tim Young
Elevate Software
www.elevatesoft.com

Sat, Jul 22 2006 5:43 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Sean


>I appreciate the input, but my understanding is that
>PostMessage will add the message to the end of the
>application's messaging queue. This means that the message
>will not execute until Application.ProcessMessages is called
>by either me or the main application thread. What I need is
>a way to force a control on a form to update (progress bar,
>graphic, caption, or anything) from another thread *without*
>the need for the application to process the messages in the
>queue. I don't want a long process in the main thread to
>make the application appear to be hung.

Errm I don't know the internals of Windows or Delphi well enough but I can assure you there's no Application.ProcessMessages in the form.

My understanding is that Application.ProcessMessages is there to FORCE Windows to process messages whilst a loop is running. If anything else were the case many of your events would never work without an Application.ProcessMessages, and its the very (or major) reason for using threads - to allow the UI to keep on working.

If the form is busy doing a loop of its own, or busy creating another thread yes the message will wait but that should be the only time.

If you'd like I'll knock up a simple demo and post it to the binaries. Mind you I see the biggest problem as finding a suitable point in the thread to pass back progress information to the main form.

Roy Lambert
Sat, Jul 22 2006 6:03 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Sean


Here's a quick demo - no application.processmessages, but with a button that will make it stop responding Smiley

There was also a very good multithread demo on Borland's website showing just about every technique. If you can't find it let me know and I'll zip it up and email it to you.

Roy Lambert





Attachments: demo.zip
Mon, Jul 24 2006 10:28 AMPermanent Link

Sean McCall
Roy,

Look at the code for Application.Run in Forms.pas. All the
application does is repeatedly process messages. While your
application is running it calls TApplication.HandleMessage
until the application is Terminated. All HandleMessage does is:

procedure TApplication.HandleMessage;
var
  Msg: TMsg;
begin
  if not ProcessMessage(Msg) then Idle(Msg);
end;

Application.ProcessMessages is just a way for you to force
the Application to process messages while your code is doing
something lengthly that will prevent control from returning
the TApplication loop:

procedure TApplication.ProcessMessages;
var
  Msg: TMsg;
begin
  while ProcessMessage(Msg) do {loop};
end;

So if you send a message to the application, it won't be
processed until the current message is completed. With few
exceptions (initialization, finalization, code added to the
project source before or after the call to Application.Run),
every line of code we write is being executed within a call
to Application.ProcessMessage. So unless there is a call to
ProcessMessage (usually done by calling
Application.ProcessMessages) from within your code, no
*Application* messages will be processed until your code
completes execution. Windows is running in a different
thread and it will continue to process messages. When your
application takes too long to process its messages, windows
says your application is not responding which doesn't
necessarily mean that it is lockup up; it just means that it
hasn't handled its messages in a while . It doesn't take a
loop to take time in your code. You can commit a lot of
changes to a record over a slow network or be writing a lot
of data using a non-thread safe third party control.
Sometimes you just don't have access to code to be able to
insert the Application.ProcessMessages. Sometimes you just
don't want the messages to be processed out of order (as an
example, it is possible that a key up code message could be
execute before you finish processing a key down if the key
down code called application.ProcessMessages and code could
be dependent on them executing in order).

BTW, if you are showing your form modally, there is a call
to HandleMessage in your form (look at
TCustomForm.ShowModal). If you just show it, the form is
displayed from within a message call and once it has been
completely displayed TApplication.HandleMessage being
repeatedly called from the loop in TApplication.Run waiting
for the next message.

Sean


Roy Lambert wrote:
> Sean
>
>
>
> Errm I don't know the internals of Windows or Delphi well enough but I can assure you there's no Application.ProcessMessages in the form.
>
> My understanding is that Application.ProcessMessages is there to FORCE Windows to process messages whilst a loop is running. If anything else were the case many of your events would never work without an Application.ProcessMessages, and its the very (or major) reason for using threads - to allow the UI to keep on working.
>
> If the form is busy doing a loop of its own, or busy creating another thread yes the message will wait but that should be the only time.
>
> If you'd like I'll knock up a simple demo and post it to the binaries. Mind you I see the biggest problem as finding a suitable point in the thread to pass back progress information to the main form.
>
> Roy Lambert
Mon, Jul 24 2006 10:37 AMPermanent Link

Sean McCall
Roy,

Thanks for taking the time to put that together, but your
demo just demonstrates what I need. You should notice that
when you click button2 that the progress bar stops moving
and then catches up when the sleep loop completes. This is
because no messages are being processed while "sleeping".
What I want is for the progress bar to keep steadily moving
while button2's event handler is sleeping. If you change the
sleep time to 25 seconds (make it 2500 ms or change the loop
to 100) you will notice that the demo appears to lock up. I
want to find some way to give a visual cue that it is not
locked up in cases where I don't have access to the code
that is causing the delay.

Sean

Roy Lambert wrote:

> Sean
>
>
> Here's a quick demo - no application.processmessages, but with a button that will make it stop responding Smiley
>
> There was also a very good multithread demo on Borland's website showing just about every technique. If you can't find it let me know and I'll zip it up and email it to you.
>
> Roy Lambert
>
>
Mon, Jul 24 2006 10:46 AMPermanent Link

Sean McCall
Tim,

Thanks. I am waiting on a new laptop to try to move up from
D6 to D2006 again. My first (2nd, 3rd, 4th) attempts on my
current desktop computer were not very successful. I'll look
at it then but I suspect that this is just surfacing some of
the code used by Synchronize. The more I look at the VCL
code the more I think that the only way to do this is to
block the main thread and take over the application's
processing of messages in the secondary thread and
selectively execute only those messages that pertain to
drawing the visual control I am trying to update. This all
gets pretty tricky because I could end up locking the
application all together if I mess up the handling of the
messages or the returning of control to the main thread.
Seems like a lot of work to get a control to paint!

Sean

Tim Young [Elevate Software] wrote:

> Sean,
>
> << What I need is a way to force a control on a form to update (progress
> bar, graphic, caption, or anything) from another thread *without* the need
> for the application to process the messages in the queue. I don't want a
> long process in the main thread to  make the application appear to be hung.
>  >>
>
> Check out the TApplication.HookSynchronizeWakeup method and WakeMainThread
> variable (Delphi 7).  That should give you what you want.
>
Mon, Jul 24 2006 11:48 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Sean


About the only thing that I can see that will work for you in all the scenarios you describe (eg the main thread of the app processing stuff that won't let messages through) is to write directly to screen memory (if that's even possible these days) and all I have to say to that is "rather you than me"!

Personally if there's stuff clogging up the foreground to that degree I'd prefer to just put up a message "go and have a nice cup of coffee and chill out for 10" <vbg>



Roy Lambert
Page 1 of 3Next Page »
Jump to Page:  1 2 3
Image