Access Violation on use OleVariant and TWebBrowser in a Thread - multithreading

i try control a TWebBrowser by OleVariant in a Thread, but i get Access Violation Error. The error only occurs when I use the following code in Delphi XE6:
var
Elements: OleVariant;
begin
Elements := Criar.Web.OleObject.document.all;
end;
Being that "Criar" is a Thread.
See the full code:
type
TCriarWeb = class(TThread)
protected procedure Execute; override;
public
Web: TWebBrowser;
end;
type
TNavegar = class(TThread)
protected procedure Execute; override;
public
end;
procedure TNavegar.Execute;
var
Criar: TCriarWeb;
Elements: OleVariant;
i: Integer;
begin
inherited;
Criar := TCriarWeb.Create;
Sleep(500);
for i := 0 to 100 do begin
Criar.Web.Navigate('http://www.google.com');
while Criar.Web.ReadyState <> READYSTATE_COMPLETE do
Sleep(100);
Elements := Criar.Web.OleObject.document.all;
end;
end;
{ TCriarWeb }
procedure TCriarWeb.Execute;
begin
inherited;
CoInitialize(nil);
Web := TWebBrowser.Create(nil);
Web.HandleNeeded;
Web.Silent := true;
while true do
Application.ProcessMessages;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Navegar: TNavegar;
begin
Navegar := TNavegar.Create;
end;
Follow the link to download the sources: https://www.dropbox.com/s/di3oou8a7ztg22m/Tentativa%20Webbrowser.rar?dl=0
I need a help for resolve this problem. Thanks

You have two major problems that I can see:
You are using COM without initializing it in TNavegar.Execute. In order to access a COM object you do need to initialize COM.
You are creating the COM object in one thread but then using it from another thread. I don't think that is going to work out for this COM object.
Keep all the access of the web browser COM object to the same thread. And once you do that you'll probably also find that you no longer need to include that extremely dubious call to Sleep. My guess is that is there to give the other thread a chance to get going and create the COM object. Any time you find yourself dealing with a threading race, Sleep is not the answer.
My guess is that you don't actually need two threads here and that one will suffice. I also wonder whether or not you really need a web browser control. Can't you do this using a simple HTTP transfer? This might very well allow you to stop calling ProcessMessages, another function that, by and large, should never be called.

Related

Delphi set thread to nil after starting it

Today I have missed a lesson and I have found a really weird line of code that I cannot understand. This is the class:
type
TMemoMessageThread = class(TThread)
strict private
FMemo: TMemo;
FMemoMessage: string;
protected
procedure Execute; override;
public
constructor Create(aMemo: TMemo);
property MemoMessage: string read FMemoMessage write FMemoMessage;
end;
Here I have created a class descending from TThread because I want to simulate a heavy computation. Look at the very simple UI.
You click the Button2"Create suspended" which creates a Suspended thread and then use this thread pressing Button3. This is the code:
constructor TMemoMessageThread.Create(aMemo: TMemo);
begin
if (aMemo = nil) then
raise Exception.Create('tMemo non valid!!!');
inherited Create(True);
FreeOnTerminate := True;
FMemo := aMemo;
end;
procedure TMemoMessageThread.Execute;
begin
Synchronize(procedure
begin
FMemo.Lines.Add('... process in parallel thread ...');
end);
Sleep(7000); //simulate something heavy to compute
Synchronize(procedure
begin
FMemo.Lines.Add(FMemoMessage + '. Done! :)');
end);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
MemoMessageThread := TMemoMessageThread.Create(Memo1);
MemoMessageThread.MemoMessage := 'Hello from TMemoMessageThread';
Button3.Enabled := true;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
MemoMessageThread.Start;
MemoMessageThread := nil; //why nil after start?
end;
As you can see, why is there a nil after the start? I really cannot understand. Since it is FreeOnTerminate shouldn't I ignore that line?
Setting MemoMessageThread to nil removes the reference to the instantiated object. The object still lives and executes, but the contact to the object is lost.
Since the thread is created with FreeOnTerminate, you should not use a reference to the object anyway. This is the only purpose of setting it to nil as I can see.
In the scope of this limited example, there indeed is no point in nil-ling the reference. It doesn't do any harm either though.
The thread is created with FreeOnTerminate set, which means that once it has started, you must not access any of its properties again, because the thread might have already been terminated and freed. Nil-ling the reference makes sure that you cannot do that, and as such is not a bad idea, but isn't necessary as long as you remember to not access the started thread instance.

Thread protecting a list of objects

I've been working with some multi-threaded applications, and part of this requires thread-protecting objects. I have individual object thread protection down by using the following method:
type
TMyClass = class(TObject)
private
FLock: TRTLCriticalSection;
FSomeString: String;
procedure Lock;
procedure Unlock;
function GetSomeString: String;
procedure SetSomeString(Value: String);
public
constructor Create;
destructor Destroy; override;
property SomeString: String read GetSomeString write SetSomeString;
end;
implementation
constructor TMyClass.Create;
begin
InitializeCriticalSection(FLock);
Lock;
try
//Initialize some stuff
finally
Unlock;
end;
end;
destructor TMyClass.Destroy;
begin
Lock;
try
//Finalize some stuff
finally
Unlock;
end;
DeleteCriticalSection(FLock);
inherited Destroy;
end;
procedure TMyClass.Lock;
begin
EnterCriticalSection(FLock);
end;
procedure TMyClass.Unlock;
begin
LeaveCriticalSection(FLock);
end;
function TMyClass.GetSomeString: String;
begin
Result:= '';
Lock;
try
Result:= FSomeString;
finally
Unlock;
end;
end;
procedure TMyClass.SetSomeString(Value: String);
begin
Lock;
try
FSomeString:= Value;
finally
Unlock;
end;
end;
However, when I implement a list of objects, I can't figure out how to safely protect each object. I create my object lists like this:
type
TMyClass = class;
TMyClasses = class;
TMyClass = class(TObject)
private
FOwner: TMyClasses;
public
constructor Create(AOwner: TMyClasses);
destructor Destroy; override;
end;
TMyClasses = class(TObject)
private
FItems: TList;
function GetMyItem(Index: Integer): TMyItem;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
function Count: Integer;
property Items[Index: Integer]: TMyClass read GetMyItem; default;
end;
implementation
{ TMyClass }
constructor TMyClass.Create(AOwner: TMyClasses);
begin
FOwner:= AOwner;
FOwner.FItems.Add(Self);
//Initialize some stuff...
end;
destructor TMyClass.Destroy;
begin
//Uninitialize some stuff...
inherited Destroy;
end;
{ TMyClasses }
constructor TMyClasses.Create;
begin
FItems:= TList.Create;
end;
destructor TMyClasses.Free;
begin
Clear;
FItems.Free;
inherited Destroy;
end;
procedure TMyClasses.Clear;
begin
while FItems.Count > 0 do begin
TMyClass(FItems[0]).Free;
FItems.Delete(0);
end;
end;
function TMyClasses.Count: Integer;
begin
Result:= FItems.Count;
end;
function TMyClasses.GetMyItem(Index: Integer): TMyClass;
begin
Result:= TMyClass(FItems[Index]);
end;
There are two ways I see doing this, and both ways I don't trust. One way would be to implement a critical section lock in the list object (TMyClasses) and each object within would share this lock (by calling FOwner.Lock; and FOwner.Unlock;. But then two different threads wouldn't even be able to work with two different objects from this list at one time, and would defeat the purpose of multithreading. The second way would be to put another critical section in each individual object of their own, but too many of these is also dangerous, right? How can I protect the list and every object in the list together?
You cannot realistically expect to use the same approach in your list class as you use in the simple class that serializes access to a single object.
For example, your list class has, like so many before it, a Count property, and an indexed Items[] property. I'm going to presume that your threading model allows the list to mutate. Now, suppose you want to write code like this:
for i := 0 to List.Count-1 do
List[i].Frob;
Suppose that another thread were to mutate the list whilst this loop was running. Well, that would clearly lead to runtime failures. So, we can conclude that the loop above would need to be wrapped with a lock. Which means that thread-safety aspects of the list must be exposed externally. You cannot keep it all internal with the current design.
If you wish to keep the lock internal to the class you'll have to remove the Count and Items[] properties. You could have your list looking like this (with some parts removed):
type
TThreadsafeList<T> = class
private
FList: TList<T>;
procedure Lock;
procedure Unlock
public
procedure Walk(const Visit: TProc<T>);
end;
....
procedure TThreadsafeList<T>.Walk(const Visit: TProc<T>);
var
Item: T;
begin
Lock;
try
for Item in FList do
Visit(Item);
finally
Unlock;
end;
end;
And now you can replace the loop above with this:
ThreadsafeList.Walk(
procedure(Item: TMyItemClass)
begin
Item.Frob;
end
);
It's not difficult to extend this concept to allow for your Walk method to support deletion of certain items, as determined by the Visit procedure.
But as you say, quite what you can do with such a list is moot. Shared data is the bane of multi-threading. I suggest you find a way to solve your problem that gives each thread its own private copy of all data that it needs. At which point you need no synchronisation and it's all good.
One final point. There is no single concept of thread safety. What is meant by thread safety varies from context to context. Eric Lippert said it best: What is this thing you call "thread safe"? So anytime you ask a question like this, you should give plenty of detail on your particular use case and threading model.

How to terminate anonymous threads in Delphi on application close?

I have a Delphi application which spawns 6 anonymous threads upon some TTimer.OnTimer event.
If I close the application from the X button in titlebar Access Violation at address $C0000005 is raised and FastMM reports leaked TAnonymousThread objects.
Which is the best way to free anonymous threads in Delphi created within OnTimer event with TThread.CreateAnonymousThread() method?
SOLUTION which worked for me:
Created a wrapper of the anonymous threads which terminates them upon being Free-ed.
type
TAnonumousThreadPool = class sealed(TObject)
strict private
FThreadList: TThreadList;
procedure TerminateRunningThreads;
procedure AnonumousThreadTerminate(Sender: TObject);
public
destructor Destroy; override; final;
procedure Start(const Procs: array of TProc);
end;
{ TAnonumousThreadPool }
procedure TAnonumousThreadPool.Start(const Procs: array of TProc);
var
T: TThread;
n: Integer;
begin
TerminateRunningThreads;
FThreadList := TThreadList.Create;
FThreadList.Duplicates := TDuplicates.dupError;
for n := Low(Procs) to High(Procs) do
begin
T := TThread.CreateAnonymousThread(Procs[n]);
TThread.NameThreadForDebugging(AnsiString('Test thread N:' + IntToStr(n) + ' TID:'), T.ThreadID);
T.OnTerminate := AnonumousThreadTerminate;
T.FreeOnTerminate := true;
FThreadList.LockList;
try
FThreadList.Add(T);
finally
FThreadList.UnlockList;
end;
T.Start;
end;
end;
procedure TAnonumousThreadPool.AnonumousThreadTerminate(Sender: TObject);
begin
FThreadList.LockList;
try
FThreadList.Remove((Sender as TThread));
finally
FThreadList.UnlockList;
end;
end;
procedure TAnonumousThreadPool.TerminateRunningThreads;
var
L: TList;
T: TThread;
begin
if not Assigned(FThreadList) then
Exit;
L := FThreadList.LockList;
try
while L.Count > 0 do
begin
T := TThread(L[0]);
T.OnTerminate := nil;
L.Remove(L[0]);
T.FreeOnTerminate := False;
T.Terminate;
T.Free;
end;
finally
FThreadList.UnlockList;
end;
FThreadList.Free;
end;
destructor TAnonumousThreadPool.Destroy;
begin
TerminateRunningThreads;
inherited;
end;
End here is how you can call it:
procedure TForm1.Button1Click(Sender: TObject);
begin
FAnonymousThreadPool.Start([ // array of procedures to execute
procedure{anonymous1}()
var
Http: THttpClient;
begin
Http := THttpClient.Create;
try
Http.CancelledCallback := function: Boolean
begin
Result := TThread.CurrentThread.CheckTerminated;
end;
Http.GetFile('http://mtgstudio.com/Screenshots/shot1.png', 'c:\1.jpg');
finally
Http.Free;
end;
end,
procedure{anonymous2}()
var
Http: THttpClient;
begin
Http := THttpClient.Create;
try
Http.CancelledCallback := function: Boolean
begin
Result := TThread.CurrentThread.CheckTerminated;
end;
Http.GetFile('http://mtgstudio.com/Screenshots/shot2.png', 'c:\2.jpg');
finally
Http.Free;
end;
end
]);
end;
No memory leaks, proper shutdown and easy to use.
If you want to maintain and exert control over a thread's lifetimes then it must have FreeOnTerminate set to False. Otherwise it is an error to refer to the thread after it has started executing. That's because once it starts executing, you've no ready way to know whether or not it has been freed.
The call to CreateAnonymousThread creates a thread with FreeOnTerminate set to True.
The thread is also marked as FreeOnTerminate, so you should not touch the returned instance after calling Start.
And so, but default, you are in no position to exert control over the thread's lifetime. However, you could set FreeOnTerminate to False immediately before calling Start. Like this:
MyThread := TThread.CreateAnonymousThread(MyProc);
MyThread.FreeOnTerminate := False;
MyThread.Start;
However, I'm not sure I would do that. The design of CreateAnonymousThread is that the thread is automatically freed upon termination. I think I personally would either follow the intended design, or derive my own TThread descendent.
To avoid errors using CreateAnonymousThread just set FreeOnTerminate to False before starting it.
This way you can work with the thread as you usually do without any workaround.
You can read the documentation that says that CreateAnonymousThread automatically sets FreeOnTerminate to True and this is what is causing the errors when you reference the thread.
Make your threads watch for some kind of notification from the outside. This could be an event that gets signaled, a message sent to a window owned by the thread, a command sent over a socket that your thread listens to, or whatever other form of communication you find.
If you determine that this problem is because your threads are so-called "anonymous" threads, then a simple workaround is for you to make them be non-anonymous threads. Put the body of the anonymous function into the Execute method, and pass any captured variables to the thread class via its constructor.

Delphi 7 + Indy + Multithread clients

My PC is connected to two electronic devices that send data using a TCP connection. I would like to develop a Delphi program able to log all this data. In the following code I create two TCPClients using two threads. I am able to log data from one device using one thread, but when two threads are runnning the application freezes. I donĀ“t have experience programming in Delphi, sorry if there are too many mistakes... I am using Delphi 7. What can I do to avoid freezing the app?
Thanks in advance
//************************************************************************//
type TThreadConn1 = class(TThread)
private
protected
procedure Execute; override;
end;
type TThreadConn2 = class(TThread)
private
protected
procedure Execute; override;
end;
var
Form1: TForm1;
TCP1: TThreadConn1;
TCP2: TThreadConn2;
flag1: bool;
flag2: bool;
implementation
{$R *.dfm}
//******************************Connection 1******************************//
procedure TThreadConn1.Execute; //Connect+loop read buffer+disconnect
begin
Form1.IdTCPClient1.Connect;
While flag1 = false do
Form1.IdTCPClient1.CurrentReadBuffer;
Form1.IdTCPClient1.Disconnect;
end;
procedure TForm1.ButtonConnection1Click(Sender: TObject);
begin
flag1:=false;
TCP1 := TThreadConn1.Create(false); //Launch thread
end;
procedure TForm1.ButtonDisconnection1Click(Sender: TObject);
begin
flag1:=true;
if (TCP1.Terminated = false) then
TCP1.Terminate; //Is it ok to finish this way a thread?
end;
//******************************Connection2******************************//
procedure TThreadConn2.Execute; //Connect+loop read buffer+disconnect
begin
Form1.IdTCPClient2.Connect;
While flag2 = false do
Form1.IdTCPClient1.CurrentReadBuffer;
Form1.IdTCPClient2.Disconnect;
end; { of procedure }
procedure TForm1.ButtonConnection2Click(Sender: TObject);
begin
flag2:=false;
TCP2 := TThreadConn2.Create(false);
end;
procedure TForm1.ButtonDisconnection2Click(Sender: TObject);
begin
flag2:=true;
if (TCP2.Terminated = false) then
TCP2.Terminate;
end;
end.
You don't need to create two separate threads that do the same thing. Create one class and then instantiate multiple copies of it. Try this instead:
type
TThreadConn = class(TThread)
private
FClient: TIdTCPClient;
protected
procedure Execute; override;
public
constructor Create(AClient: TIdTCPClient);
end;
var
TCP1: TThreadConn = nil;
TCP2: TThreadConn = nil;
constructor TThreadConn.Create(AClient: TIdTCPClient);
begin
inherited Create(False);
FClient := AClient;
end;
procedure TThreadConn.Execute;
begin
FClient.Connect;
try
while Terminated = false do
FClient.CurrentReadBuffer;
finally
FClient.Disconnect;
end;
end;
procedure TForm1.ButtonConnection1Click(Sender: TObject);
begin
TCP1 := TThreadConn.Create(TIdTCPClient1);
end;
procedure TForm1.ButtonDisconnection1Click(Sender: TObject);
begin
if (TCP1 <> nil) then
begin
TCP1.Terminate;
TCP1.WaitFor;
FreeAndNil(TCP1);
end;
end;
procedure TForm1.ButtonConnection2Click(Sender: TObject);
begin
TCP2 := TThreadConn.Create(IdTCPClient2);
end;
procedure TForm1.ButtonDisconnection2Click(Sender: TObject);
begin
if (TCP2 <> nil) then
begin
TCP2.Terminate;
TCP2.WaitFor;
FreeAndNil(TCP2);
end;
end;
Apart from the overall design, (which you will probably fix in time, maybe with some more help), in TThreadConn2.Execute, you call Form1.IdTCPClient1.CurrentReadBuffer.
Hints:
If you want to use threads to communicate with several different clients, don't plonk TidTCPClients onto forms. This is inflexible because every time you want to add a new client, you have to plonk another instance onto the form and rebuild. It also makes for a lot of extra work to get your app to shut down cleanly which, I assure you, you will not want.
Dynamically create a TidTCPClient instance in either the TThread descendant ctor or at the top of the 'Execute' method.
Try and declare one class that can be instantiated for each client connection, so you don't have to copy/paste code, edit it and and get it wrong
Oh - for now, don't try and terminate the threads at all. In fact, forever, don't try and terminate the threads at all. In the execute, loop around connect() until successful, then read stuff in a loop, write to log, (in a thread-safe manner). Do that forever. If you wnat to stop the logging, set a 'don't log' boolean so that the thread still runs but simply doesn't bother calling the logger. Again - don't go near trying to terminate the threads.
If I get a chance over the weekend, I'll do a simple example - one form, four TEdits for two pair hostname/port and two TMemos to send data to and display data from the server.

Raising Exception in TThread Execute?

I just realized that my exceptions are not being shown to the user in my threads!
At first I used this in my thread for raising the exception, which does not work:
except on E:Exception do
begin
raise Exception.Create('Error: ' + E.Message);
end;
The IDE shows me the exceptions, but my app does not!
I have looked around for a solution, this is what I found:
Delphi thread exception mechanism
http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_22039681.html
And neither of these worked for me.
Here's my Thread unit:
unit uCheckForUpdateThread;
interface
uses
Windows, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
IdHTTP, GlobalFuncs, Classes, HtmlExtractor, SysUtils, Forms;
type
TUpdaterThread = class(TThread)
private
FileGrabber : THtmlExtractor;
HTTP : TIdHttp;
AppMajor,
AppMinor,
AppRelease : Integer;
UpdateText : string;
VersionStr : string;
ExceptionText : string;
FException: Exception;
procedure DoHandleException;
procedure SyncUpdateLbl;
procedure SyncFinalize;
public
constructor Create;
protected
procedure HandleException; virtual;
procedure Execute; override;
end;
implementation
uses
uMain;
{ TUpdaterThread }
constructor TUpdaterThread.Create;
begin
inherited Create(False);
end;
procedure TUpdaterThread.Execute;
begin
inherited;
FreeOnTerminate := True;
if Terminated then
Exit;
FileGrabber := THtmlExtractor.Create;
HTTP := TIdHTTP.Create(nil);
try
try
FileGrabber.Grab('http://jeffijoe.com/xSky/Updates/CheckForUpdates.php');
except on E: Exception do
begin
UpdateText := 'Error while updating xSky!';
ExceptionText := 'Error: Cannot find remote file! Please restart xSky and try again! Also, make sure you are connected to the Internet, and that your Firewall is not blocking xSky!';
HandleException;
end;
end;
try
AppMajor := StrToInt(FileGrabber.ExtractValue('AppMajor[', ']'));
AppMinor := StrToInt(FileGrabber.ExtractValue('AppMinor[', ']'));
AppRelease := StrToInt(FileGrabber.ExtractValue('AppRelease[[', ']'));
except on E:Exception do
begin
HandleException;
end;
end;
if (APP_VER_MAJOR < AppMajor) or (APP_VER_MINOR < AppMinor) or (APP_VER_RELEASE < AppRelease) then
begin
VersionStr := Format('%d.%d.%d', [AppMajor, AppMinor, AppRelease]);
UpdateText := 'Downloading Version ' + VersionStr;
Synchronize(SyncUpdateLbl);
end;
finally
FileGrabber.Free;
HTTP.Free;
end;
Synchronize(SyncFinalize);
end;
procedure TUpdaterThread.SyncFinalize;
begin
DoTransition(frmMain.TransSearcher3, frmMain.gbLogin, True, 500);
end;
procedure TUpdaterThread.SyncUpdateLbl;
begin
frmMain.lblCheckingForUpdates.Caption := UpdateText;
end;
procedure TUpdaterThread.HandleException;
begin
FException := Exception(ExceptObject);
try
Synchronize(DoHandleException);
finally
FException := nil;
end;
end;
procedure TUpdaterThread.DoHandleException;
begin
Application.ShowException(FException);
end;
end.
If you need more info just let me know.
Again: The IDE catches all the exceptions, but my program does not show them.
EDIT: It was Cosmin's solution that worked in the end - and the reason it didn't at first, was because I didn't add the ErrMsg variable, instead I just placed whatever the variable would contain into the Synchronize, which would NOT work, however I have NO idea why. I realized it when I had no other ideas, and I just messed around with the solutions.
As always, the joke's on me. =P
Something very important you need to understand about multi-theraded development:
Each thread has its own call-stack, almost as if they're separate programs. This includes the main-thread of your program.
Threads can only interact with each other in specific ways:
They can operate on shared data or objects. This can lead to concurrency issues 'race conditions', and therefore you need to be able to help them 'share data nicely'. Which brings us to the next point.
They can "signal each other" using a variety of OS support routines. These include things like:
Mutexes
Critical Sections
Events
And finally you can send messages to other threads. Provided the thread has in some way been written to be a message receiver.
NB: Note that threads cannot strictly speaking call other threads directly. If for example Thread A tried to call Thread B directly, that would be a step on Thread A's call-stack!
This brings us to the topic of the question: "exceptions are not being raised in my threads"
The reason for this is that all an exception does is:
Record the error
And unwind the call-stack. <-- NB: Your TThread instance can't unwind the main thread's call-stack, and cannot arbitrarily interrupt the main threads execution.
So TThread will not automatically report exceptions to your main application.
You have to make the explicit decision as to how you wish to handle errors in threads, and implement accordingly.
Solution
The first step is the same as within a single threaded application. You need to decide what the error means and how the thread should react.
Should the thread continue processing?
Should the thread abort?
Should the error be logged/reported?
Does the error need a user decision? <-- This is by far the most difficult to implement, so we'll skip it for now.
Once this has been decided, implement the appropriate excpetion handler.
TIP: Make sure the exception doesn't escape the thread. The OS won't like you if it does.
If you need the main program (thread) to report the error to the user, you have a few options.
If the thread was written to return a result object, then it's easy: Make a change so that it can return the error in that object if something went wrong.
Send a message to the main thread to report the error. Note, the main thread already implements a message loop, so your application will report the error as soon as it processes that message.
EDIT: Code Sample for indicated requirement.
If all you want to do is notify the user, then Cosmind Prund's answer
should work perfectly for Delphi 2010. Older versions of Delphi need a little more work. The following is conceptually similar to Jeff's own answer, but without the mistakes:
procedure TUpdaterThread.ShowException;
begin
MessageDlg(FExceptionMessage, mtError, [mbOk], 0);
end;
procedure TUpdaterThread.Execute;
begin
try
raise Exception.Create('Test Exception');
//The code for your thread goes here
//
//
except
//Based on your requirement, the except block should be the outer-most block of your code
on E: Exception do
begin
FExceptionMessage := 'Exception: '+E.ClassName+'. '+E.Message;
Synchronize(ShowException);
end;
end;
end;
Some important corrections on Jeff's own answer, including the implementation shown within his question:
The call to Terminate is only relevant if your thread is implemented within a while not Terminated do ... loop. Take a look at what the Terminate method actually does.
The call to Exit is an unnecessary waste, but you probably did this because of your next mistake.
In your question, you're wrapping each step in its own try...except to handle the exception. This is an absolute no-no! By doing this you pretend that even though an exception occurred, everything is ok. Your thread tries the next step, but is actually guaranteed to fail! This is not the way to handle exceptions!
Here's my very, very short "take" on the issue. It only works on Delphi 2010+ (because that version introduced Anonymous methods). Unlike the more sophisticated methods already posted mine only shows the error message, nothing more, nothing less.
procedure TErrThread.Execute;
var ErrMsg: string;
begin
try
raise Exception.Create('Demonstration purposes exception');
except on E:Exception do
begin
ErrMsg := E.ClassName + ' with message ' + E.Message;
// The following could be all written on a single line to be more copy-paste friendly
Synchronize(
procedure
begin
ShowMessage(ErrMsg);
end
);
end;
end;
end;
Threads don't automatically propagate exceptions into other threads. So you must deal with it yourself.
Rafael has outlined one approach, but there are alternatives. The solution Rafael points to deals with the exception synchronously by marshalling it into the main thread.
In one of my own uses of threading, a thread pool, the threads catch and take over the ownership of the exceptions. This allows the controlling thread to handle them as it pleases.
The code looks like this.
procedure TMyThread.Execute;
begin
Try
DoStuff;
Except
on Exception do begin
FExceptAddr := ExceptAddr;
FException := AcquireExceptionObject;
//FBugReport := GetBugReportCallStackEtcFromMadExceptOrSimilar.
end;
End;
end;
If the controlling thread elects to raise the exception it can do so like this:
raise Thread.FException at Thread.FExceptAddr;
Sometimes you may have code that cannot call Synchronize, e.g. some DLLs and this approach is useful.
Note that if you don't raise the exception that was captured, then it needs to be destroyed otherwise you have a memory leak.
Well,
It is gonna be hard without your source code, but i have tested this:
How to handle exceptions in TThread objects
And it works fine. Perhaps you should take a look at it.
EDIT:
You are not following what the links you point out tell us to do. Check my link and you will see how to do that.
EDIT 2:
Try that and tell me if it worked:
TUpdaterThread= class(TThread)
private
FException: Exception;
procedure DoHandleException;
protected
procedure Execute; override;
procedure HandleException; virtual;
end;
procedure TUpdaterThread.Execute;
begin
inherited;
FreeOnTerminate := True;
if Terminated then
Exit;
FileGrabber := THtmlExtractor.Create;
HTTP := TIdHTTP.Create(Nil);
try
Try
FileGrabber.Grab('http://jeffijoe.com/xSky/Updates/CheckForUpdates.php');
Except
HandleException;
End;
Try
AppMajor := StrToInt(FileGrabber.ExtractValue('AppMajor[', ']'));
AppMinor := StrToInt(FileGrabber.ExtractValue('AppMinor[', ']'));
AppRelease := StrToInt(FileGrabber.ExtractValue('AppRelease[[', ']'));
Except
HandleException;
End;
if (APP_VER_MAJOR < AppMajor) or (APP_VER_MINOR < AppMinor) or (APP_VER_RELEASE < AppRelease) then begin
VersionStr := Format('%d.%d.%d', [AppMajor, AppMinor, AppRelease]);
UpdateText := 'Downloading Version ' + VersionStr;
Synchronize(SyncUpdateLbl);
end;
finally
FileGrabber.Free;
HTTP.Free;
end;
Synchronize(SyncFinalize);
end;
procedure TUpdaterThread.HandleException;
begin
FException := Exception(ExceptObject);
try
Synchronize(DoHandleException);
finally
FException := nil;
end;
end;
procedure TMyThread.DoHandleException;
begin
Application.ShowException(FException);
end;
EDIT 3:
You said you are no able to catch EIdHTTPProtocolException. But it works for me. Try this sample and see it for yourself:
procedure TUpdaterThread.Execute;
begin
Try
raise EIdHTTPProtocolException.Create('test');
Except
HandleException;
End;
end;
I've previously used SendMessge for inter thread communication using the TWMCopyData, so I think the following should work:
Const MyAppThreadError = WM_APP + 1;
constructor TUpdaterThread.Create(ErrorRecieverHandle: THandle);
begin
Inherited Create(False);
FErrorRecieverHandle := Application.Handle;
end;
procedure TUpdaterThread.Execute;
var
cds: TWMCopyData;
begin
try
DoStuff;
except on E:Exception do
begin
cds.dwData := 0;
cds.cbData := Length(E.message) * SizeOf(Char);
cds.lpData := Pointer(#E.message[1]);
SendMessage(FErrorRecieverHandle, MyAppThreadError, LPARAM(#cds), 0);
end;
end;
end;
I've only used it for sending simple data types or strings, but I'm sure it could be adapted send more information through as necessary.
You'll need add Self.Handle to the constructor in form created the thread and Handle the messsage in the form which created it
procedure HandleUpdateError(var Message:TMessage); message MyAppThreadError;
var
StringValue: string;
CopyData : TWMCopyData;
begin
CopyData := TWMCopyData(Msg);
SetLength(StringValue, CopyData.CopyDataStruct.cbData div SizeOf(Char));
Move(CopyData.CopyDataStruct.lpData^, StringValue[1], CopyData.CopyDataStruct.cbData);
Message.Result := 0;
ShowMessage(StringValue);
end;
Strange that everyone answered this question but failed to spot the obvious problem: given that exceptions raised in a background thread are asynchronous, and can occur at any time, this means that showing exceptions from a background thread would pop-up a dialog box at random times to the user, quite possibly showing an exception that has nothing to do with what the user is doing at the moment. I doubt that doing this could possibly enhance the user experience.

Resources