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