Secondary Thread Detection in Delphi

In Delphi 2009 and the Windows API, is there a way to detect that a particular piece of code is working in the context of a secondary stream? In pseudo code, I would like to say:

procedure DoSomething; begin if InvokedBySecondaryThread then DoIt_ThreadSafeWay else DoIt_RegularWay; end; 

This is for a journal library that I have written and used for many years, and now I'm trying to adapt to a situation where one procedure can be called from multiple threads. My "normal way" is not thread safe. I know how to make this thread safe, but I would like to use the threadafe method only when it is really needed.

Explanation (optional reading :-)

It comes down to choosing between SendMessage and PostMessage to send a registered message to multiple recipients, such as a log file, console, or VCL control. Using PostMessage means that messages will not be received while a long blocking operation is performed, which leads to an incorrect purpose of logging, especially. when used to indicate progress. I believe that I could protect the SendMessage call with a critical section, but again I would prefer to do this only when it is really needed.

I know about the global var IsMultiThread in system.pas, but this will only tell me that the application started the secondary threads. These can be streams created by third-party libraries, and therefore they can never access my code, so their existence will not affect my logging logic.

I would very much like to use the same low-level library code, regardless of whether it is called from one or more threads. For example, it would be easy to invoke modified, thread-safe logging procedures from secondary threads, but this will duplicate a lot of code, and I still have to remember that always do the right thing.

@Lieven: Currently, the logging logic looks something like this: somewhat simplified

I want the log to be as painless as possible, with minimal setup code and not worry about managing the lifetime of the object, so the library provides only a few overloaded auxiliary procedures, such as

 procedure Log( const msgText : string; level : TLogLevel = lvNotice ); overload; procedure Log( const msgText : string; Args : array of const; level : TLogLevel = lvNotice ); overload; etc, including specialized routines that log a StringList, a boolean, an Exception and so on 

Almost everything else happens when the device is implemented. All helper routines ultimately cause a call

 procedure _LogPostMessage( const msgText : string; level : TLogLevel ); 

which (a) checks if the singleton dispatcher object has been initialized; (b) instantiate the TLogMessagePacket object (container for message text, timestamp, etc.) and finally (c) send SendMessage or PostMessage to send the “packet” to the dispatcher (which has a window handle to receive it).

Then there is a group of classes originating from the abstract TLogReceiver class. One of these classes accepts registered messages and writes them to a file, the other updates TMemo, etc. I create specific recipients that I want to use in the project, and register them with the dispatcher who owns them from this point.

When the dispatcher receives the message "packet", he passes it to each receiver in turn, and then releases it.

Therefore, I am probably corrected in the above way of thinking, so I don’t quite understand your idea when you say that the code that passes the dispatcher object to the registration library should choose one depending on the context of the stream. The dispatcher is indeed the main engine of the library, and only one exists at a time.

+4
source share
2 answers
  if GetCurrentThreadID = MainThreadID then begin // in main thread end else begin // in secondary thread end; 
+14
source

At your request, I added some code examples to show what I meant.

 interface procedure Log( const msgText : string; level : TLogLevel = lvNotice; dispatcher: IDispatcher = nil); overload; ... implementation procedure Log( const msgText : string; level : TLogLevel = lvNotice; dispatcher: IDispatcher = nil); overload; var dp: IDispatcher; begin if dispatcher = nil then dp := CreateDefaultDispatcher else dp := dispatcher; dp.msgText := msgText; dp.level := level; ... end; procedure _LogPostMessage( dispatcher: IDispatcher ); begin dispatcher.LogPostMessage end; 

Now, what are you buying?

Not too much besides the added complexity, as already stated, but this makes testing _LogPostMessage easier.

Look at it this way: how are you going to write unit test to make sure the message is actually sent?

0
source

All Articles