2

应用说明:

我有一个应用程序允许用户通过线程运行多个并发查询(一次最多 100 个)。

我有一个用于记录错误的类。如果应用程序中发生错误,我会创建该类的一个实例并调用一个过程将错误写入日志文件。

问题:

我需要使错误记录代码线程安全。我注意到如果很多线程同时运行并产生相同的错误(例如无法连接到数据库),我会收到 i/o 错误 32(由于应用程序试图写入文件已经开放)。

作为一个快速而肮脏的修复,我已将写入文件的代码放在 try... 除了块内的重复循环中。如果出现异常(例如,该文件已被该类的另一个实例打开,由另一个线程启动),则它将标志设置为“false”。循环继续执行,直到标志为“真”(即没有错误写入文件),如下:

procedure TErrorLogging.logError(error: string);
var
     f: textfile;
     ok: boolean;
begin
     repeat
          ok := true;
          try
               assignfile(f, fLogFilename);
               if fileExists(fLogFilename) then append(f) else rewrite(f);
               writeln(f, error);
               closefile(f);
          except
               ok := false;
          end;
     until ok;
end;

我知道保护代码块的正确方法是使用关键部分,但我不确定如何实现它,因为有许多不同的线程使用日志记录类,并且每个实例线程有自己的日志类实例,用于写入文件(因此它们不都只是与同一块代码同步)。

我可以看到的选项:

  1. 使用上面的代码。保留此代码是否有任何问题?这是一个快速而肮脏的修复,但它有效。
  2. 使用全局 TcriticalSection(如何?)。
  3. 在某处使用单个过程来创建日志记录类的实例,线程将与之同步(我想这会破坏拥有日志记录类的对象)。
4

2 回答 2

6

每当您想要附加日志条目时创建日志记录类的实例以及一遍又一遍地打开和关闭日志文件都是错误的。我个人会使用一个内部使用字符串列表并且其基本方法是线程安全的类的实例。像这样的东西:

type
  TErrorLog = class
  private
    FList: TStringList;
    FLock: TRTLCriticalSection;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    procedure Add(const ErrorText: string);
    procedure SaveToFile(const FileName: string);
  end;

implementation

{ TErrorLog }

constructor TErrorLog.Create;
begin
  inherited Create;
  InitializeCriticalSection(FLock);
  FList := TStringList.Create;
end;

destructor TErrorLog.Destroy;
begin
  EnterCriticalSection(FLock);
  try
    FList.Free;
    inherited Destroy;
  finally
    LeaveCriticalSection(FLock);
    DeleteCriticalSection(FLock);
  end;
end;

procedure TErrorLog.Clear;
begin
  EnterCriticalSection(FLock);
  try
    FList.Clear;
  finally
    LeaveCriticalSection(FLock);
  end;
end;

procedure TErrorLog.Add(const ErrorText: string);
begin
  EnterCriticalSection(FLock);
  try
    FList.Add(ErrorText);
  finally
    LeaveCriticalSection(FLock);
  end;
end;

procedure TErrorLog.SaveToFile(const FileName: string);
begin
  EnterCriticalSection(FLock);
  try
    FList.SaveToFile(FileName);
  finally
    LeaveCriticalSection(FLock);
  end;
end;
于 2013-03-26T18:09:30.553 回答
3

不知道 Delphi,作为一般设计规则(如果可能),我会将您的 logError 函数插入到线程安全的 Array、ArrayList、Queue 对象或您可用的对象中,然后让它在后台写入文件,也许每 5-10 秒左右。这不仅应该解决 i/o 问题,而且还应该扩展到每秒数千次写入,以防您想要记录其他事件以进行调试等。

于 2013-03-26T18:01:54.013 回答