Logging and Synchronization

2019-04-01 02:16发布

问题:

I have just written my own logging framework (very lightweight, no need for a big logging framework). It consists of an interface ILogger and a number of classes implementing that interface. The one I have a question about is TGUILogger which takes a TStrings as the logging target and synchronizes the logging with the main thread so that the Items member of a listbox can be used as the target.

type
  ILogger = Interface (IInterface)
    procedure Log (const LogString : String; LogLevel : TLogLevel);
    procedure SetLoggingLevel (LogLevel : TLogLevel);
  end;

type    
  TGUILogger = class (TInterfacedObject, ILogger)
  public
    constructor Create (Target : TStrings);
    procedure Log (const LogString : String; LogLevel : TLogLevel);
    procedure SetLoggingLevel (LogLevel : TLogLevel);
  private
    procedure PerformLogging;
  end;

procedure TGUILogger.Log (const LogString : String; LogLevel : TLogLevel);
begin
  TMonitor.Enter (Self);
  try
    FLogString := GetDateTimeString + ' ' + LogString;
    TThread.Synchronize (TThread.CurrentThread, PerformLogging);
  finally
    TMonitor.Exit (Self);
  end;
end;

procedure TGUILogger.PerformLogging;
begin
  FTarget.Add (FLogString);
end;

The logging works, but the application does not close properly. It seems to hang in the Classes unit. The stack trace:

System.Halt0, System.FinalizeUnits, Classes.Finalization, Classes.FreeExternalThreads, System.TObject.Free, Classes.TThread.Destroy, Classes.TThread.RemoveQueuedEvents

What am I doing wrong here?

EDIT: I just found the following hint in the Delphi help for TThread.StaticSynchronize

Warning: Do not call StaticSynchronize from within the main thread. This can cause 
an infinite loop.     

This could be exactly my problem since some logging request come from the main thread. How can I solve this?

回答1:

If you don't find any simpler way, you could try doing this:

At program initialization, (from the main thread,) have your logging subsystem call the Windows API function GetCurrentThreadID and store the result in a variable. (EDIT: the MainThreadID variable in the System unit, gets initialized this way automatically for you at startup. Thanks, mghie.) When a logging request comes in after that, call GetCurrentThreadID again, and only synchronize if it's coming from a different thread.

There are other tricks that don't involve the Windows API, but they end up being more complicated, especially if you have a bunch of different custom TThread descendants. The basic principle is the same, though: Verify whether or not you're in the main thread before you decide whether or not to call StaticSynchronize.



回答2:

If you compare the CurrentThreadID with MainThreadID then you can choose to synchronize or not.

Personally, I chose to have the GUI ask the log system for the latest info, rather than have threads pause. Otherwise your logging interferes with the fast operation of the thread which defeats the purpose of having it.