Jump to content
PeterPanettone

Main Thread or a Background Thread?

Recommended Posts

The VCL is not thread-safe!

How to Determine (Without Code) Whether an Event Handler Runs in the Main Thread or a Background Thread?

Event Runs in Background Thread? Source
OnDeactivate YES Windows sends WM_ACTIVATEAPP
OnActivate YES Windows sends WM_ACTIVATEAPP
OnClick NO UI Thread (VCL)
OnTimer NO UI Thread
OnClose NO UI Thread
OnException NO UI Thread
 

Golden Rule

All events triggered by Windows outside the message loop run in a background thread!
 

Can anyone confirm or deny this?

EXAMPLE:

1. Declare a Global Constant in FormCreate:

private
  FMainThreadID: TThreadID;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FMainThreadID := TThread.CurrentThread.ThreadID;
  // ... Code
end;

2. Now, check in any method:

function TForm1.IsMainThread: Boolean;
begin
  Result := (TThread.CurrentThread.ThreadID = FMainThreadID);
end;
Edited by PeterPanettone

Share this post


Link to post

BTW, there already exists a global variable MainThreadID. In addition Winapi.Windows provides a function GetCurrentThreadId.

Thus the implementation of IsMainThreadID can be written as 

  Result := (GetCurrentThreadId = MainThreadID);

I also suggest to make that a global function which is available even at other places than just a form and its descendants.

 

Delphi 13 offers additional handling of accidentally doing VCL work not in the main thread. TControl has a Boolean class property RaiseOnMainThreadUsage, which is False per default. If True a call to the controls CheckNonMainThreadUsage method raises an EInvalidOperation exception when done not in the main thread. Currently TWinControl.CreateWnd is the only place where the VCL calls this itself, but it can be called at whatever place is suitable in code working with a control.

  • Like 1
  • Thanks 1

Share this post


Link to post
1 hour ago, PeterPanettone said:

Event Runs in Background Thread? Source OnDeactivate YES Windows sends WM_ACTIVATEAPP OnActivate YES Windows sends WM_ACTIVATEAPP

In my Forms, Activate and Deactivate events run in the mainthread.

 

In general, you can't rely on which thread "runs" an event.
It could change with a release or even after a patch.

Events generated by VCLs (the ones you see in the object inspector) are generally generated in the MAIN THREAD, but there are components like Indy, for example, that have a flag to generate events in a thread outside the Main Thread.

If it's important to ensure that a given code runs within the Main Thread, then you should do what you suggest (in the most up-to-date form proposed by @Uwe Raabe), which is to use a hypothetical IsMainThread.
However, it seems quite absurd to put such a thing into practice.

If you follow good programming rules, including not using VCLs in threads, I don't see why you should have any problems.

Share this post


Link to post
55 minutes ago, DelphiUdIT said:

In my Forms, Activate and Deactivate events run in the mainthread.

 

In general, you can't rely on which thread "runs" an event.
It could change with a release or even after a patch.

Events generated by VCLs (the ones you see in the object inspector) are generally generated in the MAIN THREAD, but there are components like Indy, for example, that have a flag to generate events in a thread outside the Main Thread.

If it's important to ensure that a given code runs within the Main Thread, then you should do what you suggest (in the most up-to-date form proposed by @Uwe Raabe), which is to use a hypothetical IsMainThread.
However, it seems quite absurd to put such a thing into practice.

If you follow good programming rules, including not using VCLs in threads, I don't see why you should have any problems.

You're absolutely right that many VCL events (like OnClick, OnClose) run in the main thread – that's the standard behavior.

However, OnActivate and OnDeactivate are special:
- They are triggered by WM_ACTIVATEAPP, which Windows sends in the context of the thread that owns the window.
- In practice, this is not the main thread – especially when another app activates/deactivates yours.

 

Proof (tested in Delphi 12.2):

procedure TForm1.ApplicationEvents1Deactivate(Sender: TObject);
begin
  OutputDebugString(PChar('ThreadID: ' + IntToStr(GetCurrentThreadId)));
  // → NOT equal to MainThreadID!
end;

Why it matters:

Calling Application.Minimize in OnDeactivate → crash if not in main thread.


Solution: TThread.Queue(nil, ...) – mandatory.

 

IsMainThread is not absurd – it's defensive programming.
Even if you avoid VCL in threads, Windows doesn't.


And in Delphi 13, TControl.RaiseOnMainThreadUsage := True proves:


Embarcadero agrees: thread safety is important.

Edited by PeterPanettone

Share this post


Link to post
33 minutes ago, PeterPanettone said:

Proof (tested in Delphi 12.2):

Do you have a complete test case for this?

A quick test didn't show this, but I admit that there may be scenarios where it does. Therefore my request.

Share this post


Link to post

I haven't tested this (not in front of a computer right now) but I find it hard to believe that the OnActivate/OnDeactivate events are being triggered in a background thread, given that it is message handlers in the main thread  message loop that processes them. Yes, WM_ACTIVATEAPP is sent to the active window, which could be a window in a background thread, but the TApplication and TForm windows are the ones that handle that message and they create their windows in the main thread under normal operation. You have to really go out of your way to change that behavior. 

  • Like 4

Share this post


Link to post
6 minutes ago, Remy Lebeau said:

given that it is message handlers in the main thread  message loop that processes them

In addition the WndProc handling WM_DEACTIVATE does call PostMessage(Handle, CM_DEACTIVATE, 0, 0); inside, which also places a message in the message loop and immediately returns. The OnDeactivate event is actually not called during handling the WM_DEACTIVATE but the CM_DEACTIVATE. 

 

As I still believe that Peter has encountered what he describes, I am interested in the way this could have happened.

Share this post


Link to post
21 minutes ago, Uwe Raabe said:

I am interested in the way this could have happened.

I am sorry: I was wrong. OnDeactivate runs in the MAIN THREAD! The reality is very complicated: I was relying on outdated sources to handle a complex use case, trusting something I considered defensive programming.

Sorry again, you were all right!

Share this post


Link to post
4 minutes ago, PeterPanettone said:

I am sorry: I was wrong. OnDeactivate runs in the MAIN THREAD! The reality is very complicated: I was relying on outdated sources to handle a complex use case, trusting something I considered defensive programming.

Sorry again, you were all right!

In the meantime I tried to use various codes to simulate what you had said, but everyone has given a positive result in all situations:

type
  TForm1 = class(TForm)
  private
    { Private declarations }
  protected
    class procedure APPDeactivate(Sender: TObject);
  public
    { Public declarations }
  end;

//Alternative (from HACK ideas)
procedure APPDeactivate(Sender: TObject);

var
  Form1: TForm1;

implementation

{$R *.dfm}

// Classic way around
//**************************************
class procedure TForm1.APPDeactivate(Sender: TObject);
begin
  Application.Minimize;
  if MainThreadId = GetCurrentThreadId then
    ShowMessage('YES, I am the main thread')
  else
    ShowMessage('NOOOOO, I am not the main thread')
end;
//**************************************

//Alternative (from HACK ideas)
//**************************************
procedure APPDeactivate(Sender: TObject);
begin
  Application.Minimize;
  if MainThreadId = GetCurrentThreadId then
    ShowMessage('YES, I am the main thread')
  else
    ShowMessage('NOOOOO, I am not the main thread')
end;

function ResultLikeEvent(Proc: Pointer): TMethod;
begin
  Result.Code := Proc;
  Result.Data := nil;
end;
//**************************************

initialization
  //Classic way around
  //Application.OnDeactivate := TForm1.APPDeactivate;
  //Alternative way around
  Application.OnDeactivate := TNotifyEvent(ResultLikeEvent(@AppDeactivate));
end.

 

  • Like 1

Share this post


Link to post
4 hours ago, DelphiUdIT said:

In the meantime I tried to use various codes to simulate what you had said, but everyone has given a positive result in all situations:

if Winapi.Windows.GetCurrentThreadId <> System.MainThreadID then
  ShowMessage('Background thread!')
else
  ShowMessage('Main thread!');


 

Share this post


Link to post
17 hours ago, PeterPanettone said:

Can anyone confirm or deny this?

Well, my answer is yes and no to this question ..

 

I really believe that you witnessed this and the logical thing is to deduce what you said it is not safe, yet it is impossible and should not be the case, it is absolutely safe and all these events are coming form the main thread, violating this will violate almost the Windows process-mainthread relationship, the whole thing will collapse if that happen in real life running process.

 

But what about what did you witness ?

well that is an artefact from the Delphi debugger involvement from evaluation to debug step over at some rare case, without the debugger you should never witness that, and to that extend i witnessed that in few times in the past and but it is only cosmetic.

Share this post


Link to post
8 hours ago, Kas Ob. said:

violating this will violate almost the Windows process-mainthread relationship

Windows itself doesn't really have any concept of a "main thread", and doesn't require a UI to be tied to a specific thread. Any thread can create and manage a UI window. The "main thread" relationship is more of a convention/restriction of UI frameworks, like VCL. 

  • Like 1

Share this post


Link to post
1 hour ago, Remy Lebeau said:

Windows itself doesn't really have any concept of a "main thread", and doesn't require a UI to be tied to a specific thread. Any thread can create and manage a UI window. The "main thread" relationship is more of a convention/restriction of UI frameworks, like VCL. 

The original reason I started this thread: When I restore my app programmatically (i.e., not with a mouse click on the taskbar icon and not from its initial state after program start), but e.g., with a global shortcut, then OnDeactivate is not fired when clicking another app. This seems to be a Windows restriction that requires an app to be activated by an explicit user action for OnDeactivate to work. (Not sure about this.)

Edited by PeterPanettone

Share this post


Link to post
22 minutes ago, PeterPanettone said:

When I restore my app programmatically (i.e., not with a mouse click on the taskbar icon and not from its initial state after program start), but e.g., with a global shortcut, then OnDeactivate is not fired when clicking another app.

That is a VERY different issue than which thread fires an event. 

  • Like 1

Share this post


Link to post
14 hours ago, Remy Lebeau said:

Windows itself doesn't really have any concept of a "main thread", and doesn't require a UI to be tied to a specific thread. Any thread can create and manage a UI window. The "main thread" relationship is more of a convention/restriction of UI frameworks, like VCL. 

I agree on that that there is no real distinguish between any thread running in a process, but it is more about the best practice or lets say ... well, i don't know how to formulate this right.. so i will put it in plain logic ..

 

Windows loader will create a thread and we will call it main thread, it is the initializer thread for the process, being User Process or Service .., this one will be loading the imported libraries that declared as dynamic loaded by the PE, now to the exiting/terminating part ..

All it takes and should be rightfully way to exit is for one thread to call ExitProcess, and the one calling this API will be responsible for freeing/unloading all the already loaded libraries in reverse order, while all other threads in the process will be suspended and later terminated forcefully, 

 

though it is not documented or at least i can't find a resource to suggest or point to make sure that the initial thread shold call the ExitProcess, yet it makes sense to keep it like that, we don't know if the (OS or any) libraries did initialize something and/or locked a device or did something, yet in all runtime libraries the exit loop watching and executing tied to that one, hence it better to stick the best practice for keeping and maintaining a watching and handling loop in that main thread for exit signals being message like WM_QUIT or service stop or console break signal...

 

I hope that makes sense.

Share this post


Link to post
15 hours ago, Remy Lebeau said:

That is a VERY different issue than which thread fires an event.

It is indeed. However, I needed a solution to the problem that, when PROGRAMMATICALLY restoring my application (e.g., with a global shortcut), OnDeactivate no longer fired! (Windows has internal restrictions on stealing focus.) 

However, my application has a legitimate right to do so (as it needs to be minimized when it loses the focus), so I now use the "AttachThreadInput" trick to temporarily attach my application's input thread to the foreground window's thread, thereby bypassing Windows' restrictions on stealing focus and thus making OnDeactivate fire again when the user focuses another application.

Edited by PeterPanettone

Share this post


Link to post

Does the OnActivate trigger on the programmatical restore? If not, should your app restore send a wm_activate to itself to attempt to trigger any Windows internal states?
If not, it kinda looks like a bug that OnDeactivate doesn't trigger after an app-initiated restore of the app - by timer, or by other kinds of events in the app.

 

Share this post


Link to post
6 minutes ago, Lars Fosdal said:

Does the OnActivate trigger on the programmatical restore? If not, should your app restore send a wm_activate to itself to attempt to trigger any Windows internal states?
If not, it kinda looks like a bug that OnDeactivate doesn't trigger after an app-initiated restore of the app - by timer, or by other kinds of events in the app.

 

This is not a bug. It is an internal Windows mechanism to protect the user from apps he hasn't interacted with stealing the focus. When you programmatically restore a window (like in my case with a global shortcut), Windows doesn't consistently activate the application window, so subsequent focus changes don't trigger OnDeactivate.

This means the application window is visible but not the active window. When you click another application, Windows treats it as an activation, but your app was never truly "active" in the first place, so OnDeactivate doesn't fire.

The focus-stealing prevention mechanism was introduced in Windows 2000/Windows ME (released in 2000). Before Windows 2000/ME, applications could freely call SetForegroundWindow to bring their windows to the foreground, as in Windows 95/NT4. Starting with Windows 2000/ME, Microsoft introduced restrictions that prevent background applications from forcing their windows to the foreground while the user is working with another window.

Instead of allowing applications to steal focus, SetForegroundWindow would activate the window and call FlashWindowEx to notify the user (typically causing the taskbar button to flash).

The registry hack using ForegroundLockTimeout that existed in Windows XP was actually designed to make XP behave more like Windows 2000 in preventing focus stealing. In Windows 7 and later, Microsoft claimed this protection was built in by default, though many users reported that some applications still managed to steal focus.

Share this post


Link to post

For me, RESTORE WORKS ONLY with a postmesssage. RESTORE doesn't work alone.

 

begin
  postmessage(handle, WM_ACTIVATE, 0, 0);
  Application.Restore;
end;

I simply try with a Timer and the application without that postmessage doesn't restore after been minimize.

Manually clicking on taskbar, it works (Application was restored to previous sieze and position).

 

The event DEACTIVATE (of Application) for me seems to works always.

Share this post


Link to post
9 minutes ago, DelphiUdIT said:

For me, RESTORE WORKS ONLY with a postmesssage. RESTORE doesn't work alone.

 


begin
  postmessage(handle, WM_ACTIVATE, 0, 0);
  Application.Restore;
end;

I simply try with a Timer and the application without that postmessage doesn't restore after been minimize.

Manually clicking on taskbar, it works (Application was restored to previous sieze and position).

 

The event DEACTIVATE (of Application) for me seems to works always.

Your workaround might appear to work in some scenarios, but it's fighting against the OS design rather than working with it.

The real issue is that Windows won't let you programmatically steal focus unless your application meets the conditions Windows sets (i.e., direct user interaction).

I couldn't believe it myself until I tested it.

Share this post


Link to post
On 11/8/2025 at 5:16 PM, PeterPanettone said:

Solution: TThread.Queue(nil, ...) – mandatory.

As a side note:
In such cases I prefer TThread.ForceQueue(nil, ...), in cases where it is not 100% sure, that calling this part from background or main UI thread, and could be both possible threads somehow.
With that ForceQueue you are always on the safe side.

 

Share this post


Link to post
3 hours ago, Rollo62 said:

As a side note:
In such cases I prefer TThread.ForceQueue(nil, ...), in cases where it is not 100% sure, that calling this part from background or main UI thread, and could be both possible threads somehow.
With that ForceQueue you are always on the safe side.

 

TThread.Queue(nil, ...) called on mainthread executes imediatelly, called from other threads enqueue. 

TThread.ForceQueue(nil, ...) called on mainthread or other threads  always enqueue.

Both are safe.

Share this post


Link to post
13 minutes ago, cesarliws said:

TThread.Queue(nil, ...) called on mainthread executes imediatelly, called from other threads enqueue. 

TThread.ForceQueue(nil, ...) called on mainthread or other threads  always enqueue.

Both are safe.

Right, but if you don't know or cannot for 100% ensure that you ALWAYS will be called from within the background thread,
then ForceQueue is a little safer than Queue,
because it doesn't care.
 

Share this post


Link to post

Well, there is a difference between Queue and ForceQueue when called from the main thread:

  • Queue will execute in sync, so is blocking until the task code is executed and executes the following code after the task code.
  • ForceQueue will queue the task as if it were called from another thread, thus is non-blocking. Therefore the following code is executed before the task code.

Share this post


Link to post

That is correct:
https://docwiki.embarcadero.com/Libraries/Athens/en/System.Classes.TThread.Queue

 

Quote

Warning: If the caller thread is the main thread, Queue executes the call directly (synchronously). In this case, use ForceQueue to queue the execution of a method call within the main thread.


I assume if somebody asks to "Queue" something, he would intrinsically expect that this might not naturally be synchronously.
Maybe its just me, always trying to think as simple as possible, the world is complex enough :classic_biggrin:
 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×