Why the use of a progress bar to show the progress of an iteration considerably increases the execution time of the process in question?
Considering the following example:
procedure FileToStringList(FileName: String);
var
fileSource: TStringList;
I: Integer;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
for I := 0 to fileSource.Count - 1 do
begin
//Code....
end;
finally
fileSource.Free;
end;
end;
If you add the update of a progress bar:
procedure FileToStringList(FileName: String);
var
fileSource: TStringList;
I: Integer;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
ProgressBar.Properties.Max:= fileSource.Count;
for I := 0 to fileSource.Count - 1 do
begin
Application.ProcessMessages;
ProgressBar.Position:= I;
end;
finally
fileSource.Free;
end;
end;
The time required for the iteration process to be performed is multiplied enormously.
Performing a reading test of a file of 200,000 lines, without the update of the progress bar, the time of the iteration is approximately 8 seconds but, if the update of the progress bar is activated to show the progress of the iteration, this process takes several minutes.
A test with a file of 2,700 lines, the normal time is 2-4 seconds but with the use of a progress bar, the execution time is more than 1 minute.
Can someone indicate if the use of the Application ProcessMessages is incorrect?. The result does not change if the routine is in a unit or on the same form like the progress bar.
Okay I can see the comments but, can someone indicate with an example or link which should be the correct way to update the progress bar in these conditions?
You're not really "just adding a progress bar". Your use of Application.ProcessMessages; messages means that you're also pumping all manner of additional messages doing other work. So now your "busy/main work" is competing for CPU time on the same thread (and CPU) as all the other messages going through your application. We're certainly in no position to comment on what other messages might be flowing though your application.
Busy work should not be done in the main thread. And it's generally fairly easy to wrap a method into a thread provided it isn't already too tightly coupled to your GUI.
No doubt you've been told all this in comments already.
First take note of a few important rules:
don't interact with your GUI from your child threads (Synchronise or Queue calls to code that updates GUI);
avoid sharing data1 between threads (including main thread);
if you must share data ensure your threads co-ordinate1 their access to avoid race conditions (too big a topic to go into detail).
Then the following is the minimum needed:
Define your thread.
Implement your main processing in the Execute() method.
Create and start your thread.
Since you want to update a progress bar, and remembering "Rules to note": make sure you Queue those updates.
But there are a number of more advanced considerations you can apply to improving your thread. (Those will be left to you for further research.)
You would also be well advised to also take advice Ive and others have already given and reduce the number of times you update your progress. Excessive updates just wastes time; especially with cross-thread operations (see last section).
How do you interrupt your thread if your user wants to cancel the job or close the app?
How do you manage the possibility of your user starting too many jobs?
How do you deal with errors in your threads?
How do you want to manage what happens when your thread terminates.
The following sample code is a trimmed down version of what you need in 1-4. You can fill in the trivial bits I left out.
1)
type
TFileProcessor = class(TThread)
public
constructor Create(const AFileName: string);
procedure Execute; override;
end;
2 & 4)
Note that you could pass the progress bar instance in your constructor and update it from your thread. But even though it's a little more work, it's much cleaner to define a callback event on your thread, and allow your GUI to handle the event in order to choose exactly what it wishes to do.
procedure TFileProcessor.Execute;
var
fileSource: TStringList;
I: Integer;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
{ GUI interaction must be queued.
ProgressBar.Properties.Max:= fileSource.Count;}
FPosition := 0;
FCount := fileSource.Count;
Queue(DoUpdateProgress);
for I := 0 to fileSource.Count - 1 do
begin
{ Obviously this must go!
Application.ProcessMessages;}
{ Again GUI interaction must be Queued
ProgressBar.Position:= I;}
FPosition := I;
Queue(DoUpdateProgress); {TIP: Reduce your progress updates for
more performance improvement; updating
on every single line is overkill.}
end;
finally
fileSource.Free;
end;
end;
procedure TFileProcessor.DoUpdateProgress();
begin
if Assigned(FOnUpdateProgress) then
FOnUpdateProgress(FPosition, FCount);
end;
3)
procedure TForm1.Button1Click(...);
var
LThread: TFileProcessor;
begin
LThread := TFileProcessor.Create(FFileName);
LThread.OnUpdateProgress := HandleUpdateProgress;
LThread.FreeOnTerminate := True;
LThread.Start;
end;
4)
As mentioned earlier it's cleaner if your form controls what GUI controls it wants to update and how in response to progress updates. E.g. You could update a label at the same same time if desired without any change to the thread and job code.
procedure TForm1.HandleUpdateProgress(APosition, ACount: Integer);
begin
ProgressBar.Position := APosition;
ProgressBar.Properties.Max := ACount;
Label1.Caption := Format('Line %d of %d', [APosition, ACount]);
end;
1 I'd like to emphasise the point that you should avoid sharing data with multi-threaded code. Cross-thread operations are much more expensive than same-thread operations. (This includes notifications to the main thread.)
For example, on my system, the thread code above has the following overheads.
200,000 Queued events to the main thread has an overhead of almost 1 second.
Depending on what you do inside HandleUpdateProgress you might find it takes some time processing all the queued messages for a while after your file has actually finished processing. (On my system updating a standard label and progress bar, this takes 5 seconds.)
If your application only transforms the input file, then there is no need for a special thread and you can use following code.
In the case that an application allows the user to perform another operations while processing the file, then a background thread should be used.
procedure FileToStringList(FileName: String);
var
fileSource: TStringList;
I,J: Integer;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
ProgressBar.Properties.Max:= fileSource.Count;
J:=10;//TODO make it better
for I := 0 to fileSource.Count - 1 do
begin
if (I mod J = 0) then
begin
Application.ProcessMessages;
ProgressBar.Position:= I;
end;
end;
ProgressBar.Position:= fileSource.Count;
finally
fileSource.Free;
end;
end;
Or you can call your processMessages on time:
procedure FileToStringList(FileName: String);
var
fileSource: TStringList;
I,J: Integer;
lastCheck: TDateTime;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
ProgressBar.Properties.Max:= fileSource.Count;
J:=1000;//refresh in ms
lastCheck:=now;
for I := 0 to fileSource.Count - 1 do
begin
if (lastCheck+j)<now then
begin
lastCheck:=now;
Application.ProcessMessages;
ProgressBar.Position:= I;
end;
end;
ProgressBar.Position:= fileSource.Count;
finally
fileSource.Free;
end;
end;
Related
I've created a pop-up loadscreen Form that I want to show above any other form in a Firmonkey Multi device project. Now i've run into the problem that the loadscreen doesn't get updated with the things I do in the background Form. How can I solve this?
In the code below is an example of what i've tried:
procedure TForm1.Button1Click(Sender: TObject);
var
loadScreen:TfrmLoadScreen;
begin
loadScreen := TfrmLoadScreen.Create(nil);
loadScreen.ShowModal(
procedure(ModalResult: TModalResult)
var
i:Integer;
begin
for i := 0 to 200 do
begin
loadScreen.CurrentItem := i;
loadScreen.TextMessage := 'Item:' + loadScreen.CurrentItem.ToString;
Sleep(100);
end;
ModalResult := mrCancel;
end);
end;
I guess I have to do some multi-threading, but I don't have any experience doing this! How should I do this for my loadscreen?
I've also tried the following, but the form doesn't get shown:
procedure TForm1.Button1Click(Sender: TObject);
var
loadScreen:TfrmLoadScreen;
begin
loadScreen := TfrmLoadScreen.Create(nil);
loadScreen.OnShow := FormShowLoadScreen;
loadScreen.Show;
end;
procedure TForm1.FormShowLoadScreen(Sender: TObject);
var
i:Integer;
loadScreen:TfrmLoadScreen;
begin
loadScreen := TfrmLoadScreen(Sender);
for i := 0 to 200 do
begin
loadScreen.CurrentItem := i;
Sleep(100);
end;
loadScreen.Close;
end;
In your first code block, the annonymous method is only called after loadscreen.modalresult is set to something other than 0. This never happens (that we can see)
In your second block, you have 2 different loadscreen instances. They are not the same one. The FormShowLoadScreen handler is called after the firstly loadscreen.show, but it creates a 2nd loadscreen, with it's own displays. In fact, this might happen so fast, you wouldn't see it happen.
You really need to learn more about Delphi multi-threading. To display a "progress" form, you will have to put it's processing (display updates) inside the synchronise event of a separate thread that is started just after the loadscreen form is shown.
Actually... It's actually much easier in FMX to show an animation indicator before starting an annonymous thread, and then hide it again in the thread terminate block.
See Marco Cantu's blog post here Background Operations on Delphi Android, with Threads and Timers
Since my iOS app terminates (if running in debugger, the app simply freezes without any errors) after some minutes of intensive use, I started looking into possible reasons why the app would simply suddenly close down. (But always first do so after some time.)
I create lots of shortlived threads that are set to terminate when done. (I use FreeOnTerminate := True in constructor.) However, in Delphi IDE | Thread status the threads seem to live on after execute has run.
So in debug window, I have e.g. 100 threads with State=none and Status=unknown. Am I correct in assuming that means the threads are not completely freed/gone?
For reference, demo code:
constructor TMyOnlineThread.Create(...) ;
begin
inherited Create(False);
//--
//...
//--
FreeOnTerminate := True;
end;
destructor TMyOnlineThread.Destroy;
begin
//...
inherited;
end;
procedure TMyOnlineThread.Execute;
begin
//--
// Single task. No while loop or anything like that.
//--
if (...) then
begin
if Assigned(FOnDone) then
Synchronize(MyCallBack)
;
end
;
end;
procedure someothercode;
begin
TMyOnlineThread.Create(...);
end;
I have some codes here:
procedure TForm1.Button1Click(Sender: TObject);
begin
//Some Codes(1)
Sample;
//Some Codes(2)
end;
Function Sample();
begin
sleep(5000);
end;
In this code, after //Somecodes(1) Application goes to Sample function and waits for 5 seconds then it runs //Somecodes(2) right? It means for Unfreezing Button1 we have to wait for more than 5 Seconds.
Now I want to do something that when the application runs //Some Codes(1) and Sample, Immediately goes to the next line (//Somecodes(2)) so I don't need to wait for 5 seconds to button 1 Unfreez.
How can I do it?
Like Andreas said, you can use threads for this. You might use them with an anonymous procedure like this: (Note: TThread.CreateAnonymousThread appears not to exist yet in Delphi 2010. I address this problem below.)
procedure TForm1.Button1Click(Sender: TObject);
begin
TThread.CreateAnonymousThread(
procedure
begin
// Chunk of code 1
Sleep(5000);
MessageBox(0, 'x', 'x', MB_OK);
end).Start;
TThread.CreateAnonymousThread(
procedure
begin
// Chunk of code 2
Sleep(5000);
MessageBox(0, 'y', 'y', MB_OK);
end).Start;
end;
You can also implement your own thread class, but using CreateAnonymousThread you can just pass a procedure that is executed in the thread. You can call Start (or Run) to run the thread immediately, and threads created using CreateAnonymousThread free themselves when they are done.
So, both chunks of code will run simultaneously. That means that after 5 seconds you will get two popups right after each other (you can drag aside the topmost to see the other). Also, the button will be responsive directly after it is clicked, while the sleeps are running in the background.
Note though that most of the VCL is not thread safe, so you should be careful not to modify (visual) components in these threads.
about thread safety
Like J... mentioned in the comments, it's not just the VCL that is not thread safe. Many code is not, and also your own code needs to be aware of this.
For instance if I modify the code above slightly, I can let both threads count to a billion and increment a shared integer. You'd expect the integer to reach 2 billion by the time the threads are done, but in my test it reaches just over one billion. That is because both threads are updating the same integer at the same time, overwriting the attempt of the other thread.
procedure TForm6.FormCreate(Sender: TObject);
var
n: Integer;
begin
n := 0;
TThread.CreateAnonymousThread(
procedure
var i: Integer;
begin
for i := 0 to 1000000000 do
Inc(n);
MessageBox(0, 'x', 'x', MB_OK);
end).Start;
TThread.CreateAnonymousThread(
procedure
var i: Integer;
begin
for i := 0 to 1000000000 do
Inc(n);
MessageBox(0, 'y', 'y', MB_OK);
end).Start;
Sleep(10000); // Make sure this is long enough. The number should appear later than 'x' and 'y'.
ShowMessage(IntToStr(n));
end;
To fix this, you can either synchronize the updating (execute it in the main thread), or use critical sections to lock the updates. In cases where you are actually just updating in integer, you can also use the InterlockedIncrement function. I will not explain these further, because there's plenty of proper documentation and it's beyond the scope of this answer.
Note though, that each of these methods slows down your application by executing the pieces of code after each other instead of simultaneously. You are effectively making the threads wait for each other. Nevertheless, threads are still useful, if you can finetune them so that only small pieces need to be synchronised.
TThread.CreateAnonymousThread in Delphi 2010
TThread.CreateAnonymousThread just creates an instance of TAnonymousThread, a specific thread class that executes a given procedure.
Since TThread.CreateAnonymousThread doesn't exist in Delphi 2010, you can just call it like this:
TAnonymousThread.Create(
procedure
var i: Integer;
begin
for i := 0 to 1000000000 do
Inc(n);
MessageBox(0, 'x', 'x', MB_OK);
end).Start;
I don't know if TAnonymousThread itself does exist in Delphi 2010, but if not, you can find it's code below. I hope Embarcadero doesn't mind me sharing it, but it's actually just four simple lines of code.
This class you could just as easy create yourself, but it is declared like below. The constructor takes a single parameter, which is the procedure to execute. It also sets a property that makes the thread free itself when done. The execute method just executes the given procedure.
type
TAnonymousThread = class(TThread)
private
FProc: TProc;
protected
procedure Execute; override;
public
constructor Create(const AProc: TProc);
end;
constructor TAnonymousThread.Create(const AProc: TProc);
begin
inherited Create(True);
FreeOnTerminate := True;
FProc := AProc;
end;
procedure TAnonymousThread.Execute;
begin
FProc();
end;
Consider the following program:
program TThreadBug;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes, Windows;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
procedure TMyThread.Execute;
var
i: Integer;
begin
for i := 1 to 5 do begin
Writeln(i);
Sleep(100);
end;
end;
procedure UseTThread;
var
Thread: TMyThread;
begin
Writeln('TThread');
Thread := TMyThread.Create;
Thread.Terminate;
Thread.WaitFor;
Thread.Free;
Writeln('Finished');
Writeln;
end;
procedure UseTThreadWithSleep;
var
Thread: TMyThread;
begin
Writeln('TThreadWithSleep');
Thread := TMyThread.Create;
Sleep(100);
Thread.Terminate;
Thread.WaitFor;
Thread.Free;
Writeln('Finished');
Writeln;
end;
begin
UseTThread;
UseTThreadWithSleep;
Readln;
end.
The output is:
TThread
Finished
TThreadWithSleep
1
2
3
4
5
Finished
So it seems that, for some reason, the main thread has to wait some arbitrary amount of time before terminating and waiting for the worker thread. Am I right in thinking that this is a bug in TThread? Is there any way I can work around this? I expect that if I get my thread to signal that it has started (using an event), then that would work around the issue. But that makes me feel dirty.
You can call it a bug or a TThread design flaw, the problem was discussed many times. See for example http://sergworks.wordpress.com/2011/06/25/sleep-sort-and-tthread-corner-case/
The problem is that if TThread.Terminated flag is set too early TThread.Execute method is never called. So in your case just don't call TThread.Terminate before TThread.WaitFor.
I think the reason why this happens, has been sufficiently answered by Serg's answer, but I think you should not normally call Thread.Terminate anyway. The only reason to call it, if you want the thread to terminate, for instance when the application is closing.
If you just want to wait until it is finished, you can call WaitFor (or WaitForSingleObject). This is possible, because the handle for the thread is already created in its constructor, so you can call it right away.
Also, I set FreeOnTerminate to true on these threads. Just let them run and free themselves. If I want a notification of them to be done, I can use either WaitFor or the OnTerminate event.
Here's just an example of a bunch of worker threads emptying a queue in a blocking way.
I would think you shouldn't need this, David, but maybe someone else may be happy with an example. On the other hand, you probably didn't ask this question just to have a change to rant about TThread's poor implementation, right? ;-)
First the Queue class. It's not really a traditional queue, I think. In a real multi-threaded queue, you should be able to add to the queue at any point, even when the processing is active. This queue requires you to fill its items upfront, then call the -blocking- run method. Also, the processed items are saved back to the queue.
type
TQueue = class
strict private
FNextItem: Integer;
FRunningThreads: Integer;
FLock: TCriticalSection;
FItems: TStrings; // Property...
private
// Signal from the thread that it is started or stopped.
// Used just for indication, no real functionality depends on this.
procedure ThreadStarted;
procedure ThreadEnded;
// Pull the next item from the queue.
function Pull(out Item: Integer; out Value: string): Boolean;
// Save the modified value back in the queue.
procedure Save(Item: Integer; Value: string);
public
property Items: TStrings read FItems;
constructor Create;
destructor Destroy; override;
// Process the queue. Blocking: Doesn't return until every item in the
// queue is processed.
procedure Run(ThreadCount: Integer);
// Statistics for polling.
property Item: Integer read FNextItem;
property RunningThreads: Integer read FRunningThreads;
end;
Then the Consumer thread. That one is plain and easy. It just has a reference to the queue, and an execute method that runs until the queue is empty.
TConsumer = class(TThread)
strict private
FQueue: TQueue;
protected
procedure Execute; override;
public
constructor Create(AQueue: TQueue);
end;
Here you see the implementation of this obscure 'Queue'. It's main methods are Pull and Save, which are used by the Consumer to pull the next item, and save the processed value back.
Another important method is Run, which starts a given number of worker threads and waits until all of them are finished. So this is actually a blocking method, which only returns after the queue is emptied. I'm using WaitForMultipleObjects here, which allows you to wait for upto 64 threads before you need to add extra tricks. It's the same as using WaitForSingleObject in the code in your question.
See how Thread.Terminate is never called?
{ TQueue }
constructor TQueue.Create;
// Context: Main thread
begin
FItems := TStringList.Create;
FLock := TCriticalSection.Create;
end;
destructor TQueue.Destroy;
// Context: Main thread
begin
FLock.Free;
FItems.Free;
inherited;
end;
function TQueue.Pull(out Item: Integer; out Value: string): Boolean;
// Context: Consumer thread
begin
FLock.Acquire;
try
Result := FNextItem < FItems.Count;
if Result then
begin
Item := FNextItem;
Inc(FNextItem);
Value := FItems[Item];
end;
finally
FLock.Release;
end;
end;
procedure TQueue.Save(Item: Integer; Value: string);
// Context: Consumer thread
begin
FLock.Acquire;
try
FItems[Item] := Value;
finally
FLock.Release;
end;
end;
procedure TQueue.Run(ThreadCount: Integer);
// Context: Calling thread (TQueueBackgroundThread, or can be main thread)
var
i: Integer;
Threads: TWOHandleArray;
begin
if ThreadCount <= 0 then
raise Exception.Create('You no make sense no');
if ThreadCount > MAXIMUM_WAIT_OBJECTS then
raise Exception.CreateFmt('Max number of threads: %d', [MAXIMUM_WAIT_OBJECTS]);
for i := 0 to ThreadCount - 1 do
Threads[i] := TConsumer.Create(Self).Handle;
WaitForMultipleObjects(ThreadCount, #Threads, True, INFINITE);
end;
procedure TQueue.ThreadEnded;
begin
InterlockedDecrement(FRunningThreads);
end;
procedure TQueue.ThreadStarted;
begin
InterlockedIncrement(FRunningThreads);
end;
The code for the consumer thread is plain and easy. It signals its start and end, but that's just cosmetic, because I want to be able to show the number of running threads, which is at it's max as soon as all threads are created, and only starts declining after the first thread exits (that is, when the last batch of items from the queue are being processed).
{ TConsumer }
constructor TConsumer.Create(AQueue: TQueue);
// Context: calling thread.
begin
inherited Create(False);
FQueue := AQueue;
// A consumer thread frees itself when the queue is emptied.
FreeOnTerminate := True;
end;
procedure TConsumer.Execute;
// Context: This consumer thread
var
Item: Integer;
Value: String;
begin
inherited;
// Signal the queue (optional).
FQueue.ThreadStarted;
// Work until queue is empty (Pull returns false).
while FQueue.Pull(Item, Value) do
begin
// Processing can take from .5 upto 1 second.
Value := ReverseString(Value);
Sleep(Random(500) + 1000);
// Just save modified value back in queue.
FQueue.Save(Item, Value);
end;
// Signal the queue (optional).
FQueue.ThreadEnded;
end;
Of course, if you want to view the progress (or at least a little), you don't want a blocking Run method. Or, like I did, you can execute that blocking method in a separate thread:
TQueueBackgroundThread = class(TThread)
strict private
FQueue: TQueue;
FThreadCount: Integer;
protected
procedure Execute; override;
public
constructor Create(AQueue: TQueue; AThreadCount: Integer);
end;
{ TQueueBackgroundThread }
constructor TQueueBackgroundThread.Create(AQueue: TQueue; AThreadCount: Integer);
begin
inherited Create(False);
FreeOnTerminate := True;
FQueue := AQueue;
FThreadCount := AThreadCount;
end;
procedure TQueueBackgroundThread.Execute;
// Context: This thread (TQueueBackgroundThread)
begin
FQueue.Run(FThreadCount);
end;
Now, calling this from the GUI itself. I've created a form, that holds two progress bars, two memo's, a timer and a button. Memo1 is filled with random strings. Memo2 will receive the processed strings after processing is fully done. The timer is used to update the progress bars, and the button is the only thing that actually does something.
So, the form just contains all these fields, and a reference to the queue. It also contains an event handler to be notified when processing is complete:
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Memo2: TMemo;
Timer1: TTimer;
ProgressBar1: TProgressBar;
ProgressBar2: TProgressBar;
procedure Button1Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
Q: TQueue;
procedure DoAllThreadsDone(Sender: TObject);
end;
Button1 click event, initializes the GUI, creates the queue with 100 items, and starts a background thread to process the queue. This background thread receives an OnTerminate event handler (default property for TThread) to signal the GUI when processing is done.
You can just call Q.Run in the main thread, but then it will block your GUI. If that is what you want, then you don't need this thread at all!
procedure TForm1.Button1Click(Sender: TObject);
// Context: GUI thread
const
ThreadCount = 10;
StringCount = 100;
var
i: Integer;
begin
ProgressBar1.Max := ThreadCount;
ProgressBar2.Max := StringCount;
Memo1.Text := '';
Memo2.Text := '';
for i := 1 to StringCount do
Memo1.Lines.Add(IntToHex(Random(MaxInt), 10));
Q := TQueue.Create;
Q.Items.Assign(Memo1.Lines);
with TQueueBackgroundThread.Create(Q, ThreadCount) do
begin
OnTerminate := DoAllThreadsDone;
end;
end;
The event handler for when the processing thread is done. If you want the processing to block the GUI, then you don't need this event handler and you can just copy this code to the end of Button1Click.
procedure TForm1.DoAllThreadsDone(Sender: TObject);
// Context: GUI thread
begin
Memo2.Lines.Assign(Q.Items);
FreeAndNil(Q);
ProgressBar1.Position := 0;
ProgressBar2.Position := 0;
end;
Timer is just for updating the progress bars. It fetches the number of running threads (which will only decline when processing is almost done), and it fetched the 'Item', which is actually the next item to process. So it may look finished already when actually the last 10 items are still being processed.
procedure TForm1.Timer1Timer(Sender: TObject);
// Context: GUI thread
begin
if Assigned(Q) then
begin
ProgressBar1.Position := Q.RunningThreads;
ProgressBar2.Position := Q.Item;
Caption := Format('%d, %d', [Q.RunningThreads, Q.Item]);
end;
Timer1.Interval := 20;
end;
I don't see this behavior as a bug in TThread. Execution of the new thread is supposed to occur independently of / asynchronous to the execution of the current thread. If things were set up such that the new thread was guaranteed to begin execution before TThread.Create() returns control to the caller in the current thread, that would mean execution of the new thread was (partially) synchronous to the current thread.
The new thread is added to the thread scheduling queue after the thread resources are allocated. If you're constructing a new thread from scratch (I seem to recall TThread does), this can take awhile because a lot of stuff has to be allocated behind the scenes. Avoiding this cost of getting a thread started is why ThreadPool.QueueUserWorkItem was created.
Furthermore, the behavior you're seeing fits perfectly well with the instructions you've laid out. Construct a new TThread. Immediately terminate it. Why is there any expectation that the new thread will have any opportunity to execute?
If you must have synchronous behavior around thread creation, you will at a minimum need to relinquish your remaining timeslice on the current thread. Sleep(0) will suffice. Sleep(0) gives up the rest of your current timeslice and immediately gets back into the scheduling queue behind whatever other threads (at your same priority) are waiting.
If you observe that Sleep(0) is not sufficient to getting the new thread up and running before the current thread calls Terminate, then thread creation overhead is probably preventing the new thread from getting into the thread-ready queue soon enough to satisfy your impatient current thread. In this case, try separating the overhead of thread construction from execution by constructing the new thread in the suspended state, then Start the new thread, then Sleep(0) in the current thread, then Terminate the new thread. This will give the new thread the best chance to get into the thread-ready schedule queue ahead of the current thread before the current thread terminates it.
This is as close as you're going to get to a "directed yield" in WinAPI without explicit cooperation or signaling from inside the new thread. Explicit cooperation / signalling from the new thread is the only way to guarantee that the calling thread will wait until after the new thread begins executing.
Signalling state between threads isn't dirty. What is dirty is expecting/requiring new thread construction to block the calling thread.
As already explained you must wait for the thread until it has started before calling Terminate otherwise TThread.Execute will never be called. To do so you can wait until the property TThread.Started is true.
while not Thread.Started do;
Also you can call TThread.Yield while waiting for the thread to start, because this
notifies the system that it can pass the execution to the next scheduled thread on the current processor. The operating system will select the next thread.
while not Thread.Started do
TThread.Yield;
At least we will end up with
procedure UseTThreadWithYield;
var
Thread: TMyThread;
begin
Writeln('TThreadWithYield');
Thread := TMyThread.Create;
// wait for the thread until started
while not Thread.Started do
TThread.Yield;
Thread.Terminate;
Thread.WaitFor;
Thread.Free;
Writeln('Finished');
Writeln;
end;
and a generated output like this
TThreadWithYield
1
2
3
4
5
Finished
i'm writing a delphi app that communicates with excel. one thing i noticed is that if i call the Save method on the Excel workbook object, it can appear to hang because excel has a dialog box open for the user. i'm using the late binding.
i'd like for my app to be able to notice when Save takes several seconds and then take some kind of action like show a dialog box telling this is what's happening.
i figured this'd be fairly easy. all i'd need to do is create a thread that calls Save and have that thread call Excel's Save routine. if it takes too long, i can take some action.
procedure TOfficeConnect.Save;
var
Thread:TOfficeHangThread;
begin
// spin off as thread so we can control timeout
Thread:=TOfficeSaveThread.Create(m_vExcelWorkbook);
if WaitForSingleObject(Thread.Handle, 5 {s} * 1000 {ms/s})=WAIT_TIMEOUT then
begin
Thread.FreeOnTerminate:=true;
raise Exception.Create(_('The Office spreadsheet program seems to be busy.'));
end;
Thread.Free;
end;
TOfficeSaveThread = class(TThread)
private
{ Private declarations }
m_vExcelWorkbook:variant;
protected
procedure Execute; override;
procedure DoSave;
public
constructor Create(vExcelWorkbook:variant);
end;
{ TOfficeSaveThread }
constructor TOfficeSaveThread.Create(vExcelWorkbook:variant);
begin
inherited Create(true);
m_vExcelWorkbook:=vExcelWorkbook;
Resume;
end;
procedure TOfficeSaveThread.Execute;
begin
m_vExcelWorkbook.Save;
end;
i understand this problem happens because the OLE object was created from another thread (absolutely).
how can i get around this problem? most likely i'll need to "re-marshall" for this call somehow...
any ideas?
The real problem here is that Office applications aren't intended for multithreaded use. Because there can be any number of client applications issuing commands through COM, those commands are serialized to calls and processed one by one. But sometimes Office is in a state where it doesn't accept new calls (for example when it is displaying a modal dialog) and your call gets rejected (giving you the "Call was rejected by callee"-error). See also the answer of Geoff Darst in this thread.
What you need to do is implement a IMessageFilter and take care of your calls being rejected. I did it like this:
function TIMessageFilterImpl.HandleInComingCall(dwCallType: Integer;
htaskCaller: HTASK; dwTickCount: Integer;
lpInterfaceInfo: PInterfaceInfo): Integer;
begin
Result := SERVERCALL_ISHANDLED;
end;
function TIMessageFilterImpl.MessagePending(htaskCallee: HTASK;
dwTickCount, dwPendingType: Integer): Integer;
begin
Result := PENDINGMSG_WAITDEFPROCESS;
end;
function ShouldCancel(aTask: HTASK; aWaitTime: Integer): Boolean;
var
lBusy: tagOLEUIBUSYA;
begin
FillChar(lBusy, SizeOf(tagOLEUIBUSYA), 0);
lBusy.cbStruct := SizeOf(tagOLEUIBUSYA);
lBusy.hWndOwner := Application.Handle;
if aWaitTime < 20000 then //enable cancel button after 20 seconds
lBusy.dwFlags := BZ_NOTRESPONDINGDIALOG;
lBusy.task := aTask;
Result := OleUIBusy(lBusy) = OLEUI_CANCEL;
end;
function TIMessageFilterImpl.RetryRejectedCall(htaskCallee: HTASK;
dwTickCount, dwRejectType: Integer): Integer;
begin
if dwRejectType = SERVERCALL_RETRYLATER then
begin
if dwTickCount > 10000 then //show Busy dialog after 10 seconds
begin
if ShouldCancel(htaskCallee, dwTickCount) then
Result := -1
else
Result := 100;
end
else
Result := 100; //value between 0 and 99 means 'try again immediatly', value >= 100 means wait this amount of milliseconds before trying again
end
else
begin
Result := -1; //cancel
end;
end;
The messagefilter has to be registered on the same thread as the one issuing the COM calls. My messagefilter implementation will wait 10 seconds before displaying the standard OLEUiBusy dialog. This dialog gives you the option to retry the rejected call (in your case Save) or switch to the blocking application (Excel displaying the modal dialog).
After 20 seconds of blocking, the cancel button will be enabled. Clicking the cancel button will cause your Save call to fail.
So forget messing around with threads and implement the messagefilter, which is the way
to deal with these issues.
Edit:
The above fixes "Call was rejected by callee" errors, but you have a Save that hangs. I suspect that Save brings up a popup that needs your attention (Does your workbook has a filename already?). If it is a popup that is in the way, try the following (not in a separate thread!):
{ Turn off Messageboxes etc. }
m_vExcelWorkbook.Application.DisplayAlerts := False;
try
{ Saves the workbook as a xls file with the name 'c:\test.xls' }
m_vExcelWorkbook.SaveAs('c:\test.xls', xlWorkbookNormal);
finally
{ Turn on Messageboxes again }
m_vExcelWorkbook.Application.DisplayAlerts := True;
end;
Also try to debug with Application.Visible := True; If there are any popups, there is a change you will see them and take actions to prevent them in the future.
Rather than accessing the COM object from two threads, just show the message dialog in the secondary thread. The VCL isn't thread-safe, but Windows is.
type
TOfficeHungThread = class(TThread)
private
FTerminateEvent: TEvent;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
procedure Terminate; override;
end;
...
constructor TOfficeHungThread.Create;
begin
inherited Create(True);
FTerminateEvent := TSimpleEvent.Create;
Resume;
end;
destructor TOfficeHungThread.Destroy;
begin
FTerminateEvent.Free;
inherited;
end;
procedure TOfficeHungThread.Execute;
begin
if FTerminateEvent.WaitFor(5000) = wrTimeout then
MessageBox(Application.MainForm.Handle, 'The Office spreadsheet program seems to be busy.', nil, MB_OK);
end;
procedure TOfficeHungThread.Terminate;
begin
FTerminateEvent.SetEvent;
end;
...
procedure TMainForm.Save;
var
Thread: TOfficeHungThread;
begin
Thread := TOfficeHungThread.Create;
try
m_vExcelWorkbook.Save;
Thread.Terminate;
Thread.WaitFor;
finally
Thread.Free;
end;
end;
Try calling CoInitializeEx with COINIT_MULTITHREADED since MSDN states:
Multi-threading (also called free-threading) allows calls to methods of objects created by this thread to be run on any thread.
'Marshalling' an interface from one thread to another can be done by using CoMarshalInterThreadInterfaceInStream to put the interface into a stream, move the stream to the other thread and then use CoGetInterfaceAndReleaseStream to get the interface back from the stream. see here for an example in Delphi.
Lars' answer is along the right lines I think. An alternative to his suggestion is to use the GIT (Global Interface Table), which can be used as a cross-thread repository for interfaces.
See this SO thread here for code for interacting with the GIT, where I posted a Delphi unit that provides simple access to the GIT.
It should simply be a question of registering your Excel interface into the GIT from your main thread, and then getting a separate reference to the interface from within your TOfficeHangThread thread using the GetInterfaceFromGlobal method.