スレッドセーフなIOの扱い

マルチスレッドな処理では、複数のスレッドから1つのリソースにアクセスするときがなかなか厄介です。排他処理を行わないとタイミングによってはおかしくなってしまいます。

TextWriter.Synchronized

例えば以下の処理は排他処理をせずに複数のスレッドからファイルに書き出す例です。

using (StreamWriter writer = new StreamWriter("hoge.txt"))
{
    ParallelOptions po = new ParallelOptions { MaxDegreeOfParallelism = 16 };
    Parallel.For(0, 100000, po, (i) =>
    {
        writer.WriteLine(i);
    });
}

何事もなくうまくいくときもありますが、エラーが出ることもあります。


自分で排他処理を書いてもいいのですが、このメッセージにもあるように、TextWriter.Synchronizedメソッドを使うのがダントツに楽です。

using (StreamWriter writer = new StreamWriter("hoge.txt", true))
using (TextWriter writerSync = TextWriter.Synchronized(writer))
{
    ParallelOptions po = new ParallelOptions { MaxDegreeOfParallelism = 16 };
    Parallel.For(0, 100000, po, (i) =>
    {
        writerSync.WriteLine(i);
    });
}

TraceSourceでの利用

TraceSourceを使ったログ書き出しにも使えます。今回の目的はこれでした。

TextWriter writer = new StreamWriter("log.txt");
TextWriter writerSync = TextWriter.Synchronized(writer);

TraceSource ts = new TraceSource("MyLog", SourceLevels.All);
ts.Listeners.Add(new TextWriterTraceListener(writerSync));

ParallelOptions po = new ParallelOptions { MaxDegreeOfParallelism = 16 };
Parallel.For(0, 100000, po, (i) =>
{
    ts.TraceEvent(TraceEventType.Error, 0, i.ToString());
    ts.Flush();
});

バイナリファイルでの利用

TextWriter.Synchronizedはテキスト書き出しにspecificなメソッドだそうです。StreamクラスにもSynchronizedメソッドがあります。BinaryWriterなどで使う場合にはこちらを使えばいいでしょう。

using (FileStream stream = new FileStream("hoge.dat", FileMode.Create))
using (Stream streamSync = Stream.Synchronized(stream))
using (BinaryWriter writer = new BinaryWriter(streamSync))
{
    ParallelOptions po = new ParallelOptions { MaxDegreeOfParallelism = 16 };
    Parallel.For(0, 100000, po, (i) =>
    {
        writer.Write("hogehoge\n");
    });
}

なおここではWriterばかりを扱いましたが、Readerの方でもまったく同様にできます。