Подтвердить что ты не робот

Does File.AppendAllText управляет коллизиями (т.е. Многопользовательскими concurrency)?

Вопрос

Поддерживает ли 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);
    }
}

и, как вы видите, он просто использует StreamWriter. Итак, если мы углубимся в это, в частности конструктор, который он использует, мы обнаружим, что он в конечном итоге вызывает этот конструктор:

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

и далее мы обнаруживаем, что реализация base(null) на самом деле ничего не делает, кроме как установить InternalFormatProvider на null. Итак, если мы продолжаем копать, мы обнаруживаем, что 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

Итак, теперь мы наконец-то получаем то, что мы собираемся использовать API Windows, и именно здесь вопрос действительно начинается, потому что 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, если бы у меня было два потока, пытающихся получить доступ к этому вызову в одно и то же время для одного и того же пути? Будет ли он открывать файл и буферизовать записи, чтобы оба потребителя могли писать в файл? Или мне нужно использовать объект блокировки и lock вокруг вызова AppendAllText?

4b9b3361

Ответ 1

Ключ этот метод:

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, но, вероятно, это излишне, если вы не будете писать в файл на постоянной основе (как при регистрации).

Ответ 2

Только один победит для записи, и он будет первым, любые последующие попытки потерпят неудачу, пока не будет освобождена блокировка записи (т.е. буфер сброшен и файл закрыт) - однако он может быть открыт для чтения одновременно ( разрешения зависят).

Читать - Позволяет продолжить открытие файла для чтения. Если этот флаг не указывается, любой запрос на открытие файла для чтения (этим процесс или другой процесс) завершится с ошибкой, пока файл не будет закрыт. Однако, даже если этот флаг указан, дополнительные разрешения могут все еще необходимо для доступа к файлу.