Login ProductsSalesSupportDownloadsAbout |
Home » Technical Support » DBISAM Technical Support » Support Forums » DBISAM General Discussion » View Thread |
Messages 1 to 10 of 23 total |
Need Help with Threads -- Trying to update VCL component |
Fri, Jul 21 2006 12:41 PM | Permanent 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 PM | Permanent Link |
Roy Lambert NLH Associates 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 PM | Permanent 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 PM | Permanent Link |
Tim Young [Elevate Software] Elevate Software, Inc. 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 AM | Permanent Link |
Roy Lambert NLH Associates 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 AM | Permanent Link |
Roy Lambert NLH Associates Team Elevate | Sean
Here's a quick demo - no application.processmessages, but with a button that will make it stop responding 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 AM | Permanent 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 AM | Permanent 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 > > 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 AM | Permanent 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 AM | Permanent Link |
Roy Lambert NLH Associates 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 3 | Next Page » | |
Jump to Page: 1 2 3 |
This web page was last updated on Wednesday, April 17, 2024 at 10:35 PM | Privacy PolicySite Map © 2024 Elevate Software, Inc. All Rights Reserved Questions or comments ? E-mail us at info@elevatesoft.com |