I have a question about threads and controls. I made a syncing modal dialog. There's three progressbars and couple of captions. If application is opened for a first time, then it will open syncing dialog and do the update thing. On dialog Show() method I create three different threads. Each thread controls data download, xml parsing and database inserting. And then shows progress on progressbar. All described, is working fine.
Now the problem - I want to close this dialog automatically when all items are downloaded, parsed and inserted to database. I tried to check if progressbar.position equals to progressbar.max and I tried check if threads are terminated.If I go with the progressbar way, dialog closes too early and one progressbar isn't totally ended. If I go with the thread checking way, then progressbars stop in the middle of process and that's all.
Maybe you have done it and tell the Delphi n00b, how is the best way to do it.
Thanks in advance...
For this simple thing, you can use the thread OnTerminate event (which runs in the context of the main thread) just to decrement a "thread count" variable initialized to 3 at thread creation moment.
When the thread count reaches 0, you can safely close the form.
begin
//..thread creation, stuff
FThreadCount := 3;
DownloadThread.OnTerminate := DecThreadCount;
ParseThread.OnTerminate := DecThreadCount;
InsertThread.OnTerminate := DecThreadCount;
//resume threads and other stuff
end;
procedure TForm1.DecThreadCount(Sender: TObject);
begin
Dec(FThreadCount);
if FThreadCount = 0 then
Close;
end;
Are you using Windows Vista or Windows 7? Microsoft changed the way progress bars work in Vista, so that instead of immediately jumping to the indicated position, it gradually slides towards it. This means that your progress can actually be finished, but the bar won't indicate that for another second or so, so it looks like the dialog is closed before you're done, especially if the bar has a small number of progress steps.
It's kinda ugly, but you can work around this by using a helper function that does something like this:
procedure UpdateProgressBar(bar: TProgressBar);
begin
bar.StepIt;
bar.Max := bar.Max + 1;
bar.Max := bar.Max - 1;
end;
This will ensure that it immediately jumps to the correct position.
EDIT: Details in How do I make TProgressBar stop lagging?
I'd get your threads to post a message back to the dialog when they complete. Once all three messages have been received you can close the dialog.
Related
I'm writing a small Delphi 10.3 VCL Win32 application for viewing a bunch of SVG images. Yes, there must be application that does exactly what I want but I'm using this as a bit of a learning opportunity.
It renders each image and a label indicating the image name so you can quickly scroll through them.
I decided to thread it so the user doesn't need to wait until all the images have been loaded.
This was nice except that the main form isn't painting or processing user "messages" while the loading occurs.
I'm calling the method below via the thread's Synchronize( ) method.
procedure TFinderThread.CreateControlsForNextItem;
var
img: TcxImage;
begin
svg:=TdxSmartImage.Create;
svg.LoadFromFile(m_slFiles[0]);
m_slFiles.Delete(0);
// omitted: TLabel creation
img := TcxImage.Create(m_owner);
img.Left := 0;
img.Top := m_iNextControlTop;
img.Picture.Assign(svg);
img.Parent := m_parent;
m_iNextControlTop:=m_iNextControlTop + img.Height;
svg.Free;
end;
I'd like the main form to be responsive and usable while it's being loaded. I found that a short call to Sleep( ) in the thread's Execute loop makes the UI responsive.
the Execute method contains this loop:
m_iNextControlTop:=0;
while m_slFiles.Count>0 do
begin
if Terminated then
break;
Synchronize(CreateControlsForNextItem);
sleep(1);
end;
The problem
If you use the scroll bar during refresh, some controls don't appear at all in the scroll box. The objects exist, are visible, have the correct parent, etc. This is doesn't change if you later scroll around so it doesn't appear to be a "painting" issue. It's as though the scrollbox doesn't recognize them as content.
If you don't scroll at all during refresh, all the images will appear correctly.
Any idea what's happening?
Thanks!
I would like to ask few questions, let me explain things to you first and you can see the questions below this post. I created a multi threading app which reads and update data from a database. The threads communicates with the main thread using sendmessage. I am passing a pointer TRecord to sendmessage and dispose the pointer in the main thread. Below are code snippets that shows the structure of the process:
const WM_MY_MESSAGE = WM_USER + 0;
PTestPointerRecord : ^TTestPointerRecord;
TTestPointerRecord = record
i : integer;
end;
Here is the execute event of the extended TThread class. It will run continuously unless the thread was paused or terminated.
procedure TSampleThreadClass.Execute;
var
TestPointerRecord : PTestPointerRecord;
FConnection : TConnectionObject;
FQuery : TQueryObject;
begin
while not Terminated do
begin
New(PTestPointerRecord);
FConnection := TConnectionObject.Create(nil);
FQuery := TQueryObject.Create(nil);
try
FConnection.connectionstring := 'path';
FConnection.loginPrompt := False;
FConnection.open;
FQuery.connection := FConnection;
FQuery.close;
FQuery.sql.clear;
FQuery.sql.add('select column1, column2 from table');
FQuery.open;
PTestPointerRecord.i := 0;
SendMessage(frmMain.handle, WM_MY_MESSAGE, 0, integer(PTestPointerRecord));
finally
FQuery.close;
FConnection.disconnect;
FreeAndNil(FQuery);
FreeAndNil(FConnection);
sleep(250);
end;
end;
end;
Here is the event that receives the message from the thread.
procedure TfrmMain.message(msg : TMessage);
var
TestPointerRecord : PTestPointerRecord;
begin
TestPointerRecord := PTestPointerRecord(msg.lParam);
try
edit1.Text := inttostr(TestPointerRecord.i);
finally
Dispose(TestPointerRecord);
end;
end;
The app will be used as a service type application that will run continuously all time.
Questions:
1. Am I disposing the pointer properly?
2. When I checked my task manager while the app is running, I observed that under Processes Tab, I notice that Memory(Private working set) increases continuously. Is this fine?
Regards to all
I tried suggestion of David Heffernan about using a separate handle rather than using the main form's handle. This suggestion did not really solved the problem BUT thanks to David, it is worth using since he made a big point about problems that might occur when the main form's handle was used to received a message and the window was redrew or recreated.
Through deeper exploring of my codes through, debugging, trial and error. I found that the problem was reoccurring when I create the connection and query the database. Note, I am using ZeosLib to connect to the database, and seems that every time my thread loop, do the database operations, the working private memory keeps on increasing which is I'm not sure if Zeoslib is thread safe at all. So I switched to ADO and everything went well. The working private memory stays to a stable amount.
Thanks for all the help.
Given this scenario:
procedure TForm2.Button1Click(Sender: TObject);
var
Form: TForm;
begin
Form := TForm.Create(nil);
Form.OnClose := FormClosed;
Form.Show;
Sleep(200);
TThread.CreateAnonymousThread(
procedure
begin
TThread.Synchronize( nil,
procedure
begin
Form.Close;
MessageDlg('Testing', mtInformation, [mbok], 0);
end);
end).Start;
end;
procedure TForm2.FormClosed(Sender: TObject; var Action: TCloseAction);
begin
Action := TCloseAction.caFree;
end;
My MessageDlg call is not displayed (the result of this call is always mrCancel (2)).
After digging around, it's related to the OnClose event and setting the Action to caFree.
Changing Form.Close to Form.Free and removing the OnClose event entirely displays MessageDlg ok. Placing MessageDlg before the call to Form.Close works ok. Originally I thought scope of my Form variable might have caused the problem but declaring Formas a private field in TForm2 instance doesn't solve the problem.
My aim was to display a splash form, execute my thread, then through call backs of said thread, close the Splash form and display dialog where appropriate to the user.
For clarity, why does this occur?
What is happening is that the dialog's owning window is the form that is being closed. When the dialog starts its modal message loop, the form is released and takes down its owned windows with it. Including the dialog.
Test this out, to give you more confidence that what I state above is correct, by replacing the call to show the dialog first with
MessageBox(0, ...);
and then with
MessageBox(Form.Handle, ...);
That is, be explicit about the dialog's owner.
The first version, with no owner, will show the dialog. The second won't because it replicates the scenario in your code.
The Windows runtime requires that the messages for a visual window be processed by a message loop running in the same thread that creates that window.
The Windows API's also enforce rules about what operations can be performed on a window from a thread other than that in which the window was created. i.e. very little indeed, other than sending or posting messages to it.
With that information in mind, we can explain what is occurring in your case.
The Form.Close method ultimately closes a form by posting it a message (CM_RELEASE). But in your implementation the message loop responsible for responding to that message - the application main message loop - is blocked as a result of the fact that the message is posted from within a Synchronize() method.
i.e. your Synchronize()d method posts the message to close the form, but that message cannot and will not be processed by that forms window until your Synchronize()d method has completed, and that will not occur until the user has responded to the message box you are presenting in that method.
I hope that helps you understand what is going on in your code.
I have a project for a windows service which starts a thread to do some job, this part has been working for a long time, so not part of the problem. What I am trying to do is when this job starts and ends, it should start another thread (EventMessenger that inherited TThread) to send emails with a notification about the job has started and ended. I know you can not have nested threads, but i think it should ok to start one thread from another, then it will just belong to the main process. I create the thread in suspended mode, but i am uncertain whether it is ok to call assign for objects on the thread object while it is suspended.
EventMessenger := TEventMessenger.Create(true); // true = start suspended
EventMessenger.StatusCode := AStatusCode;
EventMessenger.Receiver.Assign(Receiver);
EventMessenger.MessageOptions.Assign(MessageOptions);
EventMessenger.MessageDetails := AMessage;
EventMessenger.FreeOnTerminate := true;
EventMessenger.Resume;
The Execute for TEventMessenger sends a mail using Indy TIdSmtp, here is a part of the code
try
self.FMessage.From.Address := ASender;
self.FMessage.Recipients.EMailAddresses := AReceiver;
self.FMessage.Subject := ASubject;
self.FMessage.Body.Text := AMessage;
try
self.FSMTP.Connect;
self.FSMTP.Send(self.FMessage);
except
on E:EIdException do
begin
CurrentEurekaLogOptions.ExceptionDialogOptions := []; // Don't show dialog box
StandardEurekaNotify(E, ExceptAddr()); // Save exception to file
end;
end;
finally
if self.FSMTP.Connected then
self.FSMTP.Disconnect;
end;
The first time i start the thread EventMessenger it works fine and sends an email about the job has started. However when it starts the EventMessenger again to send a mail about the job has stopped, i got a stack overflow in ntdll. I wonder if the assign in suspended mode can mess up the stack or whether there is some problem in indy; read that it could case problem if exceptions where not masked when mixing managed/unmanaged code, not sure whether this has anything to do with it. Note: I'm not using the default Indy in Delphi 2009, as it has several bugs, I'm running with Indy10 code downloaded from their repository in January.
:779e010f ntdll.KiUserExceptionDispatcher + 0xf
:77a2878b ; ntdll.dll
:779e010f ntdll.KiUserExceptionDispatcher + 0xf
:77a2878b ; ntdll.dll
:779e010f ntdll.KiUserExceptionDispatcher + 0xf
:77a2878b ; ntdll.dll
Any one got a idea what the actually problem is that causes the stack overflow or how i can catch the exception? I have wrapped the indy send in try/except, but i guess that only works for main process not threads, so I also added a try/except around the code in the EventMesssenger.Execute which calls HandleException that I have implemented like the following code, however it service crashes with AV without entering the ExceptionHandler.
procedure TEventMessenger.DoHandleException;
begin
if FException is Exception then
begin
CurrentEurekaLogOptions.ExceptionDialogOptions := []; // Don't show dialog box
StandardEurekaNotify(FException, ExceptAddr()); // Save exception to file
end;
end;
procedure TEventMessenger.HandleException;
begin
FException := Exception(ExceptObject);
try
if not (FException is EAbort) then
Synchronize(DoHandleException);
finally
FException := nil;
end;
end;
To answer your question - Assign() will work just fine while the thread is suspended. You are not touching the stack, as Assign() is a method of TPersistent, and Delphi objects exist on the heap, not the stack.
A stack overflow usually means that you encountered a recursive function call that never ended. Run the code in the debugger and look at the call stack when the overflow occurs, that will help you diagnose which function is getting stuck in a recursive loop.
Found my answer here, seems it had to do with hard-coded breakpoint microsoft had forgot to remove.
Unhandled Exception in Rad Studio Debugger Thread
is it possible to execute a crystal report (TCrpe component) from a Delphi non VCL main thread when Output = toWindow?
I execute reports from a background thread, and when Output is toPrinter or toExport, everything is fine.
I know that creating forms in a
Delphi non VCL main thread generally is a
bad idea.
When a Crystal
Report is executed, and Output=toWindow, the component creates the output
window on its own. So I cannot prevent that the window is created by the background thread.
So is there a clean way to
execute the report in a background
thread, and display the result in a
cleanly created form?
Version: Crystal11VCL7
The following code does not work:
procedure TMyThread.Execute;
var
cr: TCrpe;
begin
inherited;
cr:= TCrpe.Create(nil);
cr.ReportName:= 'C:\TestReport.rpt';
cr.Output:= toWindow;
cr.WindowParent:= Form1; //This is the main form
cr.Execute;
end;
It seems like the report will be created and immediately destroyed afterwards.
When I enter a message loop right after cr.Execute (while true do Application.ProcessMessages; - which is obviously a very bad idea), the report window is shown nicely.
Any idea, how to do it right? Or is it simply not possible? Thanks!
I haven't had any experience with Crystal for many years (thank goodness) but in the absence of any other reply I would consider approaching the problem from a different angle.
What I do is let the report form be created in the main thread - because, as you've said, it's not a good idea to do the VCL stuff in the background - but I generate all report /data/ in a background thread. Once the data is loaded then the thread signals the main form (or whatever) via a windows message and the report goes and connects up to the dataset/datasource.
Initially I display a blank label over the client area of the report window with a message saying something like 'Report loading...' then once the message is received it hides the label and attaches the data. Works really well here and keeps the UI responsive.