C# - 为啥在使用 ReaderWriterLockSlim 时出现 LockRecursionException?

Posted

技术标签:

【中文标题】C# - 为啥在使用 ReaderWriterLockSlim 时出现 LockRecursionException?【英文标题】: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() 语句?谢谢大家! 【参考方案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;
    

【讨论】:

以上是关于C# - 为啥在使用 ReaderWriterLockSlim 时出现 LockRecursionException?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能在 C# 中使用抽象静态方法?

为啥对 C# 数组使用“新”语法?

为啥在 C# 中使用 global 关键字?

为啥在 C# 中生成的代码使用下划线? [关闭]

C# - 为啥在使用 ReaderWriterLockSlim 时出现 LockRecursionException?

调用方法并将返回值分配给数组时,为啥C#在调用方法时使用数组引用?