首页 > 解决方案 > 多线程环境中的最佳实践 EventWaitHandle.Dispose()

问题描述

我正在创建一个小型记录器。为此,我有一个LogFile发布该Log(ILogCsvLine csvLine)方法的类 ( )。此方法将要记录的行添加到队列 ( linesToLog) 并设置一个触发器,该触发器已使用Logging(..)as 方法注册到 ThreadPool,只要有触发器和待处理的处理器时间,就应该在不同的线程上执行该方法。该Logging(..)方法正在写入要记录到给定文件的行。现在我遇到了问题,该Dispose()方法已被调用,而队列不为空,导致对该Logging(..)方法的调用,而triggerfileAccessLock已被处置。作为一个解决方案,我围绕这些进行了一些检查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);
        }
    }
}

标签: c#multithreadingdisposeevent-wait-handle

解决方案


推荐阅读