17

質問

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)だけで実際には何もしないことがわかります。したがって、掘り続けると、次のことがわかります。InternalFormatProvidernullCreateFile

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。かなり長い方法ですが、私が本当に興味を持っているのは次の 1 行です。

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)

では、2 つのスレッドが同じパスに対して同時にその呼び出しにアクセスしようとした場合、Windows はどうするでしょうか? 両方のコンシューマがファイルに書き込めるように、ファイルを開いて書き込みをバッファリングしますか? lockそれとも、ロック オブジェクトとへの呼び出しを活用する必要がありAppendAllTextますか?

4

3 に答える 3

7

書き込みに勝つのは 1 つだけで、それが最初になります。書き込みロックが解放されるまで (つまり、バッファーがフラッシュされ、ファイルが閉じられるまで)、後続の試みはすべて失敗します。 .

読み取り- 読み取りのためにファイルを後で開くことができます。このフラグが指定されていない場合、(このプロセスまたは別のプロセスによって) 読み取り用にファイルを開く要求は、ファイルが閉じられるまで失敗します。ただし、このフラグが指定されている場合でも、ファイルにアクセスするには追加のアクセス許可が必要になる場合があります。

于 2013-04-18T12:58:48.440 に答える
5

鍵はこの方法です:

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開いています。つまり、他のスレッドまたはプロセスはファイルを読み取り用に開くことができますが、他のプロセス/スレッドは書き込み用に開くことはできません。

おそらく、複数の同時ライターを許可したくないでしょう。2 つの非常に大きなバッファーを書き込むことを検討してください。それらがインターリーブされることになる可能性が非常に高いです。

そのため、はい...そのファイルに追加される可能性のある複数のスレッドがある場合は、おそらくロックを使用してアクセスを同期する必要があります。

アプリケーションによっては、キューからテキストを読み取ってファイルに追加するコンシューマ スレッドを用意するという別のオプションもあります。そうすれば、1 つのスレッドだけがファイルにアクセスできます。他のスレッドは、書き込みスレッドが処理するキューにメッセージを書き込みます。これは で行うのは非常に簡単ですがBlockingCollection、(ロギングのように) 継続的にファイルに書き込んでいない限り、おそらくやり過ぎです。

于 2013-04-18T13:09:53.633 に答える