开发者

Detecting secondary thread context in Delphi

In Delphi 2009 and Windows API, is there a way to detect that a particular piece of code is running in the context of a secondary thread? In pseudocode, I would like to be able to say:

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

This is for a logging library which I wrote and have used for years, and am now trying to adapt to a situation where on开发者_StackOverflow中文版e procedure can be invoked from multiple threads. My "regular way" is not threadsafe. I know how to make it threadsafe, but I'd like to use the threadsafe method only when actually necessary.

Explanation (not a required reading :-)

It boils down to a choice between using SendMessage and PostMessage to dispatch a logged message to multiple receivers, such as a logfile, console or a VCL control. Using PostMessage means that the messages will not be received while a long blocking operation is in progress, which defeats the purpose of logging somewhat, esp. when used to indicate progress. I suppose I could protect the SendMessage call with a critical section, but again I would prefer to have to do it only when truly required.

I know about the global var IsMultiThread in system.pas, but this will only tell me that the application has started secondary threads. These may be threads created by 3rd party libraries, and as such they will never access "my" code, so their existence will not affect my logging logic.

I really wish I could use the same low-level library code regardless of whether it is invoked from one or multiple threads. It would be easy for example to call modified, threadsafe logging procedures from inside secondary threads, but this would duplicate much code, and I'd still have to remember to always do the right thing.

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

I want the logging to be as painless as possible, with minimal setup code and no worrying about managing object lifetime, so the library only exposes a number of overloaded helper 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

Pretty much everything else happens in the implementation of the unit. All the helper routines eventually end up calling

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

which (a) checks if the singleton dispatcher object has been initialized; (b) creates an instance of a TLogMessagePacket object (container for the message text, timestamp etc.), and finally (c) does a 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 descended from an abstract TLogReceiver class. One such class receives the logged messages and writes them to file, another updates a TMemo, etc. I instantiate the concrete receivers I want to use in a project, and register them with the dispatcher, which owns them from that point on.

When the dispatcher receives a message "packet", it hands it off to each receiver in turn, then frees it.

So I am probably fixed in the above way of thinking, which is why I am not quite getting your idea when you say code handing a dispatcher object to the logging library should choose one depending on thread context. The dispatcher is really the main engine of the library, and only one exists at a time.


  if GetCurrentThreadID = MainThreadID then begin
// in main thread
  end
  else begin
// in secondary thread
  end; 


At your request, I've added some sample code 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 does this buy you?

Not a whole lot besides added complexity as already pointed out but it makes for easier testing of the _LogPostMessage.

Look at it this way: How are you going to write a unit test to verify that a message indeed gets posted?

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜