.NET 2.0:File.AppendAllText(...) - 线程安全实现

16

作为一种闲暇好奇心的练习,考虑以下简单的日志记录类:

internal static class Logging
{
    private static object threadlock;

    static Logging()
    {
        threadlock = new object(); 
    }

    internal static void WriteLog(string message)
    {
        try
        {
            lock (threadlock)
            {
                File.AppendAllText(@"C:\logfile.log", message);
            }
        }
        catch
        {
            ...handle logging errors...
        }
    }
}

File.AppendAllText(...)这个方法的实现本身是否具有线程安全性?需要在其周围使用lock吗?

搜索关于这个问题的信息会得到很多矛盾的信息,有些人说需要,有些人说不需要。MSDN没有提到。

3个回答

21

File.AppendAllText 调用时会获取一个独占式写锁来访问日志文件,这会导致任何并发线程试图访问该文件都会抛出异常。因此,确实需要一个静态锁对象来防止多个线程同时尝试写入日志文件并引发 IOException 异常。

如果这将成为一个问题,我建议记录到数据库表中,这将更好地处理并发的日志写入。

或者,您可以使用TextWriterTraceListener,它是线程安全的(也就是说,它会为您执行锁定;我尽量少编写自己的多线程代码)。


1
这是矛盾的。如果File.AppendAllText有一个独占写锁,为什么还需要单独的锁定? - iheanyi
5
你误解了,被锁定的是文件的写入权限。所以任何其他同时尝试进行写入的线程都会收到一个异常。为了防止这种情况发生,你必须在程序中使用锁来序列化写操作。 - Pradeep
1
如果你使用的是重型关系型数据库,不要浪费宝贵的数据库资源来记录日志;但如果你使用的是轻量级的NoSQL数据库,那么在数据库中记录日志也无妨。如果你想本地记录日志,可以使用类似于Windows事件日志的东西,但不要自己写入文件系统。如果你需要线程安全和非阻塞,为什么不每个日志都写一个新文件,而不是追加到同一个文件中呢? - Timothy Gonzalez

2

测试并行写入操作表明,如果您注释掉锁定语句,将会出现System.IO.IOException。

[Test]
public void Answer_Question()
{
    var ex = Assert.Throws<AggregateException>(() => Parallel.Invoke(
        () => Logging.WriteLog("abc"),
        () => Logging.WriteLog("123")
    ));

    // System.IO.IOException: The process cannot access the file 'C:\Logs\thread-safety-test.txt' because it is being used by another process.
    Console.Write(ex);
}

0

从线程安全的角度来看,它以读共享方式打开文件,因此假设您的文件系统支持文件锁定,则一次只允许一个线程写入该文件。然而,如果其他线程试图读取相同的文件,则可能会获得脏读取。


10
当其他线程尝试写入时,会抛出异常。 - Adam Robinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接