【问题标题】:C# - Why I get LockRecursionException when using ReaderWriterLockSlim?C# - 为什么在使用 ReaderWriterLockSlim 时出现 LockRecursionException?
【发布时间】:2020-08-18 21:33:28
【问题描述】:

我正在开发一个多线程程序。我正在尝试使用两种类型的日志创建一个简单的日志记录系统:“INFO”和“ERROR”。 我创建了一个名为“Logger”的类和两个静态方法:Info 和 Error。我还创建了一个静态 ReadWriterLockSlim,因此一次只有一个线程可以写入日志文件。

日志记录工作了一段时间,但几分钟后我得到了这个异常:

System.Threading.LockRecursionException: 'Recursive write lock acquisitions not allowed in this mode.'

这是 Logger 类的代码:

public class Logger
{
    const string filePath = "Log.txt";
    static private ReaderWriterLockSlim writeLock = new ReaderWriterLockSlim();
    public static string Info(string log)
    {
        StringBuilder sb = new StringBuilder();
        string logMessage = DateTime.Now.ToString() + " | INFO | " + log;
        writeLock.EnterWriteLock();
        using (StreamWriter writetext = File.AppendText(filePath))
        {
            writetext.WriteLine(logMessage);
            Console.WriteLine(logMessage);
        }
        writeLock.ExitWriteLock();
        return logMessage;
    }

    public static string Error(string log)
    {
        StringBuilder sb = new StringBuilder();
        string logMessage = DateTime.Now.ToString() + " | ERROR | " + log;
        writeLock.EnterWriteLock();
        using (StreamWriter writetext = File.AppendText(filePath))
        {
            writetext.WriteLine(logMessage);
            Console.WriteLine(logMessage);
        }
        return logMessage;
    }

}

希望有人可以帮助我了解我做错了什么以及如何解决它。 提前谢谢大家。

【问题讨论】:

  • 在您的Error 方法中,您进入了写锁,但再也不会退出它。当同一个线程尝试进入InfoError 并再次获取锁时,您将得到该异常。您需要确保始终在获取锁后释放锁(最好使用 try/catch,以防写入消息引发异常)
  • 另外,既然你从来没有读过,那么有一个读/写锁的原因是什么?这是一种专门的锁,它允许同时一个读取器或多个写入器。看起来您不能同时拥有多个编写器,因此您没有使用锁。从您发布的代码中,您可以使用简单的lock statement
  • Error 不释放锁一样,这两种方法都没有使用try-finally 来确保在整个写入日志文件时抛出异常时释放锁。如果在写入日志条目时随机发生 I/O 异常,您会泄漏锁并导致同样的问题。
  • @canton7 是的,我认为它解决了它。我的错。
  • @MartinCostello 谢谢马丁。你能告诉我一个我应该如何使用 try-finally 的例子吗?我还接受了 canton7 的建议,并将读取器/写入器锁更改为简单的 lock() 语句。你将如何使用 try-finally 和 lock() 语句?谢谢大家!

标签: c# multithreading logging locking streamwriter


【解决方案1】:

您的问题出在您的 Error 方法中:它获取了锁,但没有再次释放它。

另外,您在获取锁时确实应该使用try/finally。这确保了如果在写日志消息时抛出异常,锁总是被释放:

public static string Error(string log)
{
    StringBuilder sb = new StringBuilder();
    string logMessage = DateTime.Now.ToString() + " | ERROR | " + log;
    writeLock.EnterWriteLock();
    try
    {
        using (StreamWriter writetext = File.AppendText(filePath))
        {
            writetext.WriteLine(logMessage);
            Console.WriteLine(logMessage);
        }
    }
    finally
    {
        writeLock.ExitWriteLock();
    }
    return logMessage;
}

也就是说,读/写锁是一种专用锁,它允许单个写入者或多个读取者同时访问资源。您没有任何读者,因此使用读/写锁没有意义。只需使用普通锁:

public class Logger
{
    const string filePath = "Log.txt";
    private static readonly object lockObject = new object();
    public static string Info(string log)
    {
        StringBuilder sb = new StringBuilder();
        string logMessage = DateTime.Now.ToString() + " | INFO | " + log;
        lock (lockObject)
        {
            using (StreamWriter writetext = File.AppendText(filePath))
            {
                writetext.WriteLine(logMessage);
                Console.WriteLine(logMessage);
            }
        }
        return logMessage;
    }

    public static string Error(string log)
    {
        StringBuilder sb = new StringBuilder();
        string logMessage = DateTime.Now.ToString() + " | ERROR | " + log;
        lock (lockObject)
        {
            using (StreamWriter writetext = File.AppendText(filePath))
            {
                writetext.WriteLine(logMessage);
                Console.WriteLine(logMessage);
            }
        }
        return logMessage;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多