File.AppendAllText是否管理冲突(即多用户并发)

本文关键字:多用户 并发 冲突 AppendAllText 是否 管理 File | 更新日期: 2023-09-27 18:09:46

问题

File.AppendAllText是否管理来自多个写入程序的冲突?

研究

我注意到MSDN文档并没有真正提供任何位置,所以我决定反映代码,看看它能做什么。以下是来自File.AppendAllText:的调用方法

private static void InternalAppendAllText(string path, string contents, Encoding encoding)
{
    using (StreamWriter streamWriter = new StreamWriter(path, true, encoding))
    {
        streamWriter.Write(contents);
    }
}

正如您所看到的,它只是利用了CCD_ 3。因此,如果我们深入研究一下,特别是它使用的构造函数,我们会发现它最终调用了这个构造函数:

internal StreamWriter(string path, bool append, Encoding encoding, int bufferSize, bool checkHost) : base(null)
{
    if (path == null)
    {
        throw new ArgumentNullException("path");
    }
    if (encoding == null)
    {
        throw new ArgumentNullException("encoding");
    }
    if (path.Length == 0)
    {
        throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"));
    }
    if (bufferSize <= 0)
    {
        throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
    }
    Stream streamArg = StreamWriter.CreateFile(path, append, checkHost);
    this.Init(streamArg, encoding, bufferSize, false);
}

具有以下值:

path:        the path to the file
append:      the text to append
encoding:    UTF8NoBOM
bufferSize:  1024
checkHost:   true

并且我们进一步发现CCD_ 4实现除了将CCD_ 5设置为CCD_。所以,如果我们继续挖掘,我们会发现CreateFile:

private static Stream CreateFile(string path, bool append, bool checkHost)
{
    FileMode mode = append ? FileMode.Append : FileMode.Create;
    return new FileStream(path, mode, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan, Path.GetFileName(path), false, false, checkHost);
}

创建具有以下参数值的FileStream

path:         the path to the file
mode:         FileMode.Append
access:       FileAccess.Write
share:        FileShare.Read
bufferSize:   4096
options:      FileOptions.SequentialScan
msgPath:      just the file name of the path provided
bFromProxy:   false
useLongPath:  false
checkHost:    true

因此,现在我们终于有所进展,因为我们即将利用Windows API,而这正是问题真正开始的地方,因为FileStream::ctor调用了一个名为Init的方法。这是一个相当长的方法,但我真的对一行感兴趣:

this._handle = Win32Native.SafeCreateFile(text3,
    dwDesiredAccess,
    share,
    secAttrs,
    mode,
    num,
    IntPtr.Zero);

它当然调用CreateFile,其中参数值为:

text3:            the full path to the file
dwDesiredAccess:  1073741824
share:            1 (FILE_SHARE_READ)
secAttrs:         null
mode:             4 (OPEN_ALWAYS)
num:              134217728 | 1048576 (FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_POSIX_SEMANTICS)

那么,如果我有两个线程试图同时访问同一路径的调用,Windows会怎么办?它会打开文件并缓冲写入,这样两个使用者都可以写入文件吗?还是我需要在调用AppendAllText时利用锁定对象和lock

File.AppendAllText是否管理冲突(即多用户并发)

只有一个会赢得写入,它将是第一个,任何后续尝试都将失败,直到释放写锁(即刷新缓冲区并关闭文件(-但是,它可以同时打开以进行读取(取决于权限(。

读取-允许随后打开文件进行读取。如果此标志未指定,则任何打开文件进行读取的请求(通过进程或另一进程(将失败,直到关闭该文件为止。但是,即使指定了此标志,其他权限也可能仍然需要访问该文件。

关键是这个方法:

private static Stream CreateFile(string path, bool append, bool checkHost)
{
    FileMode mode = append ? FileMode.Append : FileMode.Create;
    return new FileStream(path, mode, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan, Path.GetFileName(path), false, false, checkHost);
}

它是用FileShare.Read打开的,这意味着其他线程或进程可以打开文件进行读取,但没有其他进程/线程可以打开它进行写入。

您可能不希望它允许多个并发编写器。考虑写两个非常大的缓冲区。它们很可能最终会被交错。

所以,是的。。。如果有多个线程可能附加到该文件,则需要同步访问,可能需要使用锁。

根据应用程序的不同,另一种选择是使用一个使用者线程从队列中读取文本并附加到文件中。这样,只有一个线程可以访问该文件。其他线程将消息放入编写器线程服务的队列中。使用BlockingCollection可以很容易地做到这一点,但除非您在连续的基础上(如日志记录中(对文件进行写入,否则这可能会有些过头。

我知道这个话题很老,但我在阅读后发现https://stackoverflow.com/a/18692934/3789481

通过实现EventWaitHandle,我可以很容易地防止与File.AppendAllText 发生冲突

    EventWaitHandle waitHandle = new EventWaitHandle(true, EventResetMode.AutoReset, "SHARED_BY_ALL_PROCESSES");
    Task[] tasks = new Task[ThreadCount];
    for (int counter = 0; counter < ThreadCount; counter++)
    {
        var dividedList = ....
        tasks[counter] = await Task.Factory.StartNew(async () => await RunTask(counter, dividedList, waitHandle));
    }

和RunTask写入文件

    private static async Task RunTask(int threadNum, List<string> ids, EventWaitHandle waitHandle)
    {
        Console.WriteLine($"Start thread {threadNum}");
        foreach (var id in ids)
        {
            // start waiting
            waitHandle.WaitOne();
            File.AppendAllText(@".'Result.txt", text + Environment.NewLine);
            waitHandle.Set();
            // until release
            // ninja code
        }
        Console.WriteLine($"End thread {threadNum}");
    }

我已经用500个线程进行了测试,效果很好!!