我正在创建一个小型记录器。为此,我有一个LogFile
发布该Log(ILogCsvLine csvLine)
方法的类 ( )。此方法将要记录的行添加到队列 ( linesToLog
) 并设置一个触发器,该触发器已使用Logging(..)
as 方法注册到 ThreadPool,只要有触发器和待处理的处理器时间,就应该在不同的线程上执行该方法。该Logging(..)
方法正在写入要记录到给定文件的行。现在我遇到了问题,该Dispose()
方法已被调用,而队列不为空,导致对该Logging(..)
方法的调用,而trigger
或fileAccessLock
已被处置。作为一个解决方案,我围绕这些进行了一些检查EventWaitHandles
,并想知道是否有更好的可读性和更优雅的方式来做到这一点。
internal sealed class LogFile : ILogFile
{
private readonly EventWaitHandle fileAccessLock = new AutoResetEvent(true);
private readonly IFilesLoader filesLoader;
private readonly Queue<ILogCsvLine> linesToLog = new Queue<ILogCsvLine>();
private readonly IFile logFile;
private readonly object myLock = new object();
private RegisteredWaitHandle registeredWait;
private readonly EventWaitHandle trigger = new AutoResetEvent(false);
private IDirectory directory = null;
private bool disposeFileAccessLock = false;
private bool disposeTrigger = false;
private bool isDisposed = false;
private bool isDisposing = false;
private bool isLogging = false;
private bool isSettingTrigger = false;
public event EventHandler<FilesException> LoggingFailed;
public LogFile(IFile logFile, IFilesLoader filesLoader)
{
this.filesLoader = filesLoader;
this.logFile = logFile;
Setup();
}
private void Setup()
{
directory = logFile.ParentDirectory;
EnforceFileExists();
registeredWait = ThreadPool.RegisterWaitForSingleObject(trigger, Logging, null, -1, false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~LogFile()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (!isDisposed)
{
try
{
lock (myLock)
isDisposing = true;
registeredWait?.Unregister(trigger);
if (disposing)
{
if (isSettingTrigger)
disposeTrigger = true;
else
trigger?.Dispose();
if (isLogging)
disposeFileAccessLock = true;
else
fileAccessLock?.Dispose();
}
}
finally
{
isDisposed = true;
lock (myLock)
isDisposing = false;
}
}
}
public IFile File => logFile;
public void Log(ILogCsvLine csvLine)
{
lock (myLock)
{
if (isDisposing || isDisposed)
return;
linesToLog.Enqueue(csvLine);
isSettingTrigger = true;
}
trigger.Set();
lock (myLock)
{
isSettingTrigger = false;
if (disposeTrigger)
trigger?.Dispose();
}
}
private void Logging(object data, bool timedOut)
{
ILogCsvLine line = null;
lock (myLock)
{
if (linesToLog.Count == 0)
return;
if (isDisposing || isDisposed)
return;
line = linesToLog.Dequeue();
isLogging = true;
}
fileAccessLock.WaitOne();
FilesException occurredException = null;
IStreamWriter sw = null;
try
{
EnforceFileExists();
sw = logFile.AppendText();
do
{
sw.WriteLine(line.ToCsvString());
lock (myLock)
{
if (linesToLog.Count > 0)
line = linesToLog.Dequeue();
else
line = null;
}
} while (line != null);
}
catch (Exception e)
{
if (e is ThreadAbortException)
throw;
string message = string.Format("Error writing to {0}. {1}", logFile.Path, e.Message);
occurredException = new FilesException(message, e);
}
finally
{
if (sw != null)
{
sw.Flush();
sw.Close();
sw = null;
}
}
fileSizeManager?.Check();
fileAccessLock.Set();
lock (myLock)
{
if (disposeFileAccessLock)
fileAccessLock?.Dispose();
isLogging = false;
}
if (occurredException != null)
LoggingFailed?.Invoke(this, occurredException);
}
private void EnforceFileExists()
{
if (!directory.Exists)
directory.Create();
if (!logFile.Exists)
{
var fileAccess = filesLoader.GetFileAccess();
fileAccess.Create(logFile.Path, FileSystemRights.Read | FileSystemRights.Write);
}
}
}