写入日志时出现 C# 锁定问题

C# lock issues when writing to log

我有一个 class,它使用 Nlog 框架以编程方式创建日志。我有几个进程 运行 同时创建它们的日志。我在 class 的构造函数中添加了一把锁,因为之前两个线程试图同时创建一个文件,这导致了一些烦人的错误(比如只创建一个日志)。

这似乎解决了那个问题。但是现在我在写入日志时遇到了同样的问题,并且使用锁也没有帮助。这里是class。

public class CwiLogger
    {
        private Logger _log;
        private static Object createLogLock = new Object();
        private static Object writeLogLock = new Object();
        public CwiLogger(string logPath, string logName, string className)
        {
            lock (createLogLock)
            {
                var config = new LoggingConfiguration();
                var fileTarget = new FileTarget();
                config.AddTarget("file", fileTarget);
                fileTarget.FileName = Path.Combine(logPath, logName);
                fileTarget.Layout = "${longdate}|${level:uppercase=true}|${logger}|${message}";
                var rule = new LoggingRule("*", LogLevel.Debug, fileTarget);
                config.LoggingRules.Add(rule);

                LogManager.Configuration = config;

                this._log = LogManager.GetLogger(className);
            }
        }

        public void AddToLog(string logText, LogLevel level = null)
        {
            lock (writeLogLock)
            {
                level = level ?? LogLevel.Info;
                this._log.Log(level, logText + "\n");
            }
        }
    }

在我的客户端代码中,我 运行 两个线程,每个线程 运行 宁一个看起来像这样的进程:

var log = new CwiLogger(@"C:\Users\jma\Documents\ProgrammingJunk\logTest", "Log2.txt", "Log2");
for (int i = 0; i < 100; i++)
{
    log.AddToLog("Log2 " + i);
}

我只有一个使用 log1,另一个使用 log2。

在我的输出中。 2 个日志中的一个总是成功地计数到 99,而另一个得到 4-5,然后没有其他输出。

这是因为 lock() 语法编译为 Monitor.EnterMonitor.Leave - Monitor.Enter 如果当前线程无法锁定对象,持有对象的线程将进入休眠状态锁定,然后在调用 Monitor.Leave.

时唤醒所有等待的线程

你的问题是你的第二个线程在尝试锁定对象之前必须经过额外的唤醒时间,此时刚刚释放锁的线程已经再次锁定了它。

如果您希望您的例程只锁定很短的时间(太短以至于不想让等待线程休眠),请改用 SpinLock。 class 让每个线程在循环中不断尝试锁定直到成功,而不是休眠。这意味着它使用更多 CPU(因此笔记本电脑上的电池电量更多),这就是为什么您锁定的代码必须 运行 非常短的时间。

也许是这样的:

public class CwiLogger
{
    private static LoggingConfiguration _logConfig = new LoggingConfiguration();
    private static Object createLogLock = new Object();
    private Logger _log;

    public CwiLogger(string logPath, string logName, string className)
    {
            lock (createLogLock)
            {
                var fileTarget = _logConfig.FindTargetByName(logName);
                if (fileTarget == null)
                {
                    var fileTarget = new FileTarget(logName);
                    fileTarget.FileName = Path.Combine(logPath, logName);
                    fileTarget.Layout = "${longdate}|${level:uppercase=true}|${logger}|${message}";
                    _logConfig.AddTarget(fileTarget);
                    var rule = new LoggingRule(className, LogLevel.Debug, fileTarget) { Final = true };
                    _logConfig.LoggingRules.Add(rule);
                    LogManager.Configuration = _logConfig;
                }
            }
        this._log = LogManager.GetLogger(className);
    }

    public void AddToLog(string logText, LogLevel level = null)
    {
        level = level ?? LogLevel.Info;
        this._log.Log(level, logText + "\n");
    }
}

或者从这里窃取一些想法:https://github.com/NLog/NLog/issues/1998