【问题标题】:Logging and Synchronization记录和同步
【发布时间】:2009-03-18 16:42:30
【问题描述】:

我刚刚编写了自己的日志框架(非常轻量级,不需要大型日志框架)。它由一个接口 ILogger 和许多实现该接口的类组成。我有一个问题是 TGUILogger,它将 TStrings 作为日志记录目标,并将日志记录与主线程同步,以便列表框的 Items 成员可以用作目标。

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;

日志记录有效,但应用程序未正确关闭。它似乎挂在 Classes 单元中。堆栈跟踪:

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

我在这里做错了什么?

编辑:我刚刚在 TThread.StaticSynchronize 的 Delphi 帮助中找到了以下提示

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

这可能正是我的问题,因为一些日志记录请求来自主线程。我该如何解决这个问题?

【问题讨论】:

  • 我不想回答题外话,但是如果你真的想使用它并且需要它轻量级,请考虑重新设计它,或者使用一些现成的日志库。这段代码有很多问题,远非轻量级。 mj2008 的回答是朝着正确的方向迈出第一步。
  • 有趣的是,就在今天,我添加了一些与我自己的日志记录框架类似的东西(最终使用 SmartInspect 进行实际的日志记录)......
  • 你添加了什么?希望通过调用 TGUILogger.Log() 不会出现同样的死锁可能性?
  • 为什么是 TGuiLogger 之前的类?我假设一个错字,因为它不会编译。
  • @Gamecat:确实,我时不时会回到 C++ 习惯 @mghie:不是跑题。但是日志主要用于记录错误,因此这里的性能不是太关键。我只是想要一个简单易用的实现。

标签: multithreading delphi synchronization delphi-2009


【解决方案1】:

如果您将 CurrentThreadID 与 MainThreadID 进行比较,那么您可以选择是否同步。

就个人而言,我选择让 GUI 向日志系统询问最新信息,而不是让线程暂停。否则,您的日志记录会干扰线程的快速操作,从而破坏了拥有它的目的。

【讨论】:

  • +1,很遗憾我只能投票一次。这两段都值得他们投赞成票,第二段远不止一个。您不希望在日志框架中出现减速、线程序列化和强制上下文切换。使用 Synchronize() 可以为您提供所有三个。真是一场噩梦。
  • +1 好答案。关于你的第二点以及我选择以我的方式实现它的原因:我希望将所有日志记录在一个地方,这样我就可以拥有 GUI 记录器、文件记录器、电子邮件记录器等,而无需更改 GUI。如果设计干净且易于更改,我可以忍受不太好的性能。
【解决方案2】:

如果你没有找到更简单的方法,你可以尝试这样做:

在程序初始化时,(从主线程)让您的日志子系统调用 Windows API 函数 GetCurrentThreadID 并将结果存储在一个变量中。 (编辑:系统单元中的 MainThreadID 变量,在启动时会自动为您初始化。谢谢,mghie。)当日志请求进入之后,再次调用 GetCurrentThreadID,并且仅在它来自不同线程时进行同步。

还有其他不涉及 Windows API 的技巧,但它们最终会变得更加复杂,尤其是当您有一堆不同的自定义 TThread 后代时。不过基本原理是一样的:在决定是否调用 StaticSynchronize 之前,请先验证您是否在主线程中。

【讨论】:

  • 啊,这就是“任何更简单的方法”。我认为必须有一种内置方法来获取主线程的 ID,但我不知道 MainThreadID。我会编辑我的回复。但是,TThread.ThreadID 的问题在于您需要引用当前线程才能使用它。
  • 啊,是的,这确实是个问题。但无论如何,线程 ID 值仅用于比较,因为 Delphi RTL 缺少 IsMainThread() 函数或类似的东西。那将是一个有用的补充……就像 13 年前一样?使用 Delphi 2?
  • 是的。我个人很惊讶没有 Application.ThreadID 属性,甚至没有 Application.MainThread: TThread 属性。在某些情况下,这些都会很有用......
  • 我刚刚意识到 TThread.ThreadId 无论如何在这里都没用,因为不能保证在正确的线程上下文中调用线程方法 - 它可以从任何线程调用。需要什么:返回“当前”TThread 实例(或 nil)的 TThread 类方法。
  • Re Application.ThreadId: MainThreadId 也可以在控制台应用程序中工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多