Root > Advanced topics > Multi-threaded applications > Creating threads > TThread Class

TThread Class

Previous pageReturn to chapter overviewNext page   

Important: as a general rule, you should write your application in a such way that it will behave correctly without EurekaLog on board. This means that if your or 3rd party code throws an exception in a thread - it must be handled in a way that you expect. For example, by showing an error message, canceling action, retrying, etc. Once your application behaves like it is supposed to do - then you add EurekaLog for it. EurekaLog will auto-handle all unhandled exceptions automatically (by hooking few well-known places), and for everything else - you can call EurekaLog from your own handlers when needed. In other words:

Incorrect: your application does not show messages for exceptions in thread and you are adding EurekaLog in hopes to fix this behavior;
Correct: your application correctly handles thread exceptions and you are adding EurekaLog to receive reports about such exceptions.

 

TThread class is convenient wrapper for BeginThread function. TThread is an abstract class that enables creation of separate threads of execution in an application. Create a descendant of TThread to represent an execution thread in a multithreaded application. Each new instance of a TThread descendant is a new thread of execution. Multiple instances of a TThread derived class make an application multithreaded. Define the thread object's Execute method by inserting the code that should execute when the thread is executed.

 

Important note: it is recommended to use TThreadEx class instead of TThread class.

 

Note: when your thread is processing exception, the processing will be performed in a background thread. EurekaLog's event handlers will be called from a background thread. You need to use some sort of synchronization if you are accessing global data in your EurekaLog's event handlers. This also means that several threads can process multiple exceptions simultaneously at the same time. You can avoid this by marshaling processing to main thread. For example, you can use TThread(Ex) and analyze .FatalException property in your main thread. See below for examples. Alternatively, you may use "Consecutive processing" option.

 

TThread class handles all exceptions in thread function (a.k.a. Execute method) by saving exception into FatalException property. FatalException property can be analyzed after thread's termination.

 

Important note: TThread does not invoke any exception handling routine, it just merely saves exception into FatalException property. This means that TThread silently hides all exceptions by default. You have to analyze FatalException property manually.

 

EurekaLog does not contain any hook for TThread - because TThread properly handles exceptions (even though it does not invoke any exception handler, but expects this from its caller). However, there is a "Auto-handle TThread exceptions" option. This option will call EurekaLog as default exception handler for TThread. Even though this option is a fast and convenient way to handle TThread's exception - it is not recommended to use for the following reasons:

This option will break any 3rd party code which uses TThread class and expects FatalException property to behave as usual;
This option have no effect if EurekaLog is not enabled in your application. Therefore you still have to write proper exception handling code if you're going to compile your application without EurekaLog;

Therefore it is recommended to keep "Auto-handle TThread exceptions" option turned off and use proper exception handling as dictated by TThread's design (see below).

 

There is at least five different ways to handle exceptions in TThread:

 

Note: very old Delphi versions do not have some important features of TThread. For example, you can use only method #5 in Delphi 4 - because TThread in Delphi 4 lacks FatalException property and there is no proper exception support at all.

 

Important note: EurekaLog has to be enabled for background threads.

 

Important note: turning off low-level hooks means that EurekaLog will not install additional hooks for API functions. This means that EurekaLog will not intercept important system calls. For example, EurekaLog will not hook ExitThread function, which means EurekaLog will not know when a thread exits. This will lead to thread information stored forever - until application terminates. You can call internal _NotifyThreadGone or _CleanupFinishedThreads functions (from EThreadsManager unit) to notify EurekaLog about thread's termination. Such manual notifications can be avoided by using EurekaLog's wrappers (TThreadEx, for example).

 


 

#1: Re-raise exception in caller thread (recommended)

A common practice with easy approach is a simple re-raise of FatalException in the caller thread:

 

type
  TMyThread = class(TThread)
  protected
    procedure Execute; override;
  end;
 
procedure TMyThread.Execute;
begin
  // 1. Name the thread for easy identification in debugger and bug reports
  NameThread('This is my thread');

 
  // 2. Activate EurekaLog for this thread.
  SetEurekaLogStateInThread(0, True); 

 
  // 3. <- ... your thread code ...
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Thread: TMyThread;
  E: TObject;
begin
  // Create thread
  Thread := TMyThread.Create(False);
  try

 
    // Wait for thread's completion. 
    // This wait can be implemented in any other way.

    // E.g. you can assign OnTerminate handler;

    // or you can PostMessage from thread to main thread.
    Thread.WaitFor;
 
    // Analyze thread completion.
    // Re-raise any thread error in current thread.

    // You should do this only after the thread has finished.

 

    // Clear the FatalException property,

    // so it won't be deleted when thread is deleted
    E := Thread.FatalException;

    PPointer(@Thread.FatalException)^ := nil

 

    // Re-raise thread's exception 

    if Assigned(E) then
      raise E;
 
    // What if there is no exception, but thread failed?

    if Thread.ReturnValue <> 0 then
      Abort;

 

    // Success

    // Do something with thread here...

    // ...

 
  finally
    FreeAndNil(Thread);
  end;
end;

 

This code "analyzes" thread exception by taking it out from the thread and re-raising it into caller thread. This is an easy way to deal with exceptions from TThread. Exception will be handled by caller thread in a usual way (e.g. via already established exception handler). Note that normal execution path of the caller thread will be stopped - thus your code will not continue if there was an error in the background thread. A call stack for the exception will be call stack for the background thread - even though background thread is already terminated, and exception is re-raised.

 

Note that exception will be processed by a caller thread (which is main thread in the example above).

 

 

#2: Handle exception in caller thread

Alternative approach could be the following:

 

type
  TMyThread = class(TThread)
  protected
    procedure Execute; override;
  end;
 
procedure TMyThread.Execute;
begin
  // 1. Name the thread for easy identification in debugger and bug reports
  NameThread('This is my thread');

 
  // 2. Activate EurekaLog for this thread.
  SetEurekaLogStateInThread(0, True); 

 

  // 3. <- ... your thread code ...
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Thread: TMyThread;
  E: TObject;
begin
  // Create thread
  Thread := TMyThread.Create(False);
  try

 
    // Wait for thread's completion. 
    // This wait can be implemented in any other way.

    // E.g. you can assign OnTerminate handler;

    // or you can PostMessage from thread to main thread.
    Thread.WaitFor;
 
    // Analyze thread completion.
    // You should do this only after the thread has finished.
    if Assigned(Thread.FatalException) then

    begin
      HandleException(Thread.FatalException, False, atThread);

      Abort; // thread failed - stop right here

    end;

 

    // What if there is no exception, but thread failed?

    if Thread.ReturnValue <> 0 then
      Abort;

 

    // Success

    // Do something with thread here...

    // ...

 
  finally
    FreeAndNil(Thread);
  end;
end;

 

We can analyze failure in-place instead of re-raising exception. We use HandleException function from EBase unit to handle exceptions. See also.

 

Please note that we can not use ApplicationHandleException routine as we are going to analyze saved/non-raised exception. ApplicationHandleException routine always analyze active exception, it does not allow you to specify which exception should be analyzed. HandleException function will show standard error dialog if EurekaLog was not enabled. HandleException function will show EurekaLog error dialog if EurekaLog was enabled. EBase unit is a special unit that can be included in any application without including full EurekaLog code. See also: how to handle an exception.

 

Note that we need to manually abort normal execution path (via Abort routine) to avoid continue working as if there was no error.

 

Note that exception will be processed by a caller thread (which is main thread in the example above).

 

 

#3: Handle exception in OnTerminate event handler

Sometimes it is not a very convenient to use WaitFor in main thread to wait for thread's completion. You can use events for such cases:

 

type
  TMyThread = class(TThreadEx)
  private
    procedure HandleThreadException(Sender: TObject);
  protected
    procedure Execute; override;
  end;
 
procedure TMyThread.Execute;
begin
  // 1. Name the thread for easy identification in debugger and bug reports
  NameThread('This is my thread');

 
  // 2. Activate EurekaLog for this thread.
  SetEurekaLogStateInThread(0, True); 

 

  // 3. <- ... your thread code ...
end;
 

// Called within Synchronize from main thread
procedure TMyThread.HandleThreadException(Sender: TObject);
begin
  if Assigned(FatalException) then
    HandleException(FatalException, False, atThread);
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Thread: TMyThread;
begin
  Thread := TMyThread.Create(True, 'My thread');
  Thread.OnTerminate := Thread.HandleThreadException;
  Thread.FreeOnTerminate := True;
  Thread.Start;  // = Resume for old Delphi versions
  Thread := nil// never access thread var with FreeOnTerminate after Start
end;

 

This code will analyze thread exception by invoking exception handler for FatalException property. We install OnTerminate event handler to automatically analyze thread exception on thread's termination. Note that exception will be processed by main thread. See also: how to handle an exception.

 

 

#4: Handle exception by overriding DoTerminate method

You can also override DoTerminate method instead of assigning OnTerminate handler. For example:

 

type
  TMyThread = class(TThreadEx)
  private
    procedure DoTerminate; override;
  protected
    procedure Execute; override;
  end;
 
procedure TMyThread.Execute;
begin

  // 1. Name the thread for easy identification in debugger and bug reports
  NameThread('This is my thread');

 
  // 2. Activate EurekaLog for this thread.
  SetEurekaLogStateInThread(0, True); 

 

  // 3. <- ... your thread code ...
end;
 

// Called by a background thread!

// E.g. this call is NOT synchronized into main thread
procedure TMyThread.DoTerminate;
begin

  inherited;
  if Assigned(FatalException) then
    HandleException(FatalException, False, atThread);
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Thread: TMyThread;
begin
  Thread := TMyThread.Create(True, 'My thread');
  Thread.FreeOnTerminate := True;
  Thread.Start;  // = Resume for old Delphi versions
  Thread := nil// never access thread var with FreeOnTerminate after Start
end;

 

This code will work in the same way as the previous example. The only difference is that exception will be processed by this thread (previous example processed exception in main thread). See also: how to handle an exception.

 

 

#5: Handle exception with explicit try/except block

Surely, you can also use previous approach with explicit try/except block for TThread class:

 

type
  TMyThread = class(TThreadEx)
  protected
    procedure Execute; override;
  end;
 
procedure TMyThread.Execute;
begin
  try
    // 1. Name the thread for easy identification in debugger and bug reports
    NameThread('This is my thread');

 
    // 2. Activate EurekaLog for this thread.
    SetEurekaLogStateInThread(0, True); 

 

    // 3. <- ... your thread code ...
  except
    // Handle any exception in thread
    ApplicationHandleException(nil); // from Classes unit
  end;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Thread: TMyThread;
begin
  Thread := TMyThread.Create(True, 'My thread');
  Thread.FreeOnTerminate := True;
  Thread.Start;
  Thread := nil// never access thread var with FreeOnTerminate after Start
end;

 

Note that exception will be processed by the background thread (e.g. not synchronized into main thread). See also: how to handle an exception.

 

 

Important notes:

EurekaLog has to be enabled for background threads.
it is recommended to use TThreadEx class instead of TThread class.

 

 

See also:




Send feedback... Build date: 2024-09-30
Last edited: 2023-07-12
PRIVACY STATEMENT
The documentation team uses the feedback submitted to improve the EurekaLog documentation. We do not use your e-mail address for any other purpose. We will remove your e-mail address from our system after the issue you are reporting has been resolved. While we are working to resolve this issue, we may send you an e-mail message to request more information about your feedback. After the issues have been addressed, we may send you an email message to let you know that your feedback has been addressed.


Permanent link to this article: https://www.eurekalog.com/help/eurekalog/multithreading_tthread.php