.NETにおけるファイル出力とマルチスレッド
複数のスレッドから、単一のTextWriterオブジェクトを使用してファイル出力しようとすると、通常例外が発生する。
using (var writer = new StreamWriter(...))
{
Parallel.For(0, 10000, i => writer.WriteLine(i));
}
この例外メッセージにあるとおり、.NETには
TextWriter.Synchronized(TextWriter)
TextReader.Synchronized(TextReader)
という、TextWriter/TextReaderを受け取って、排他制御をおこなうラッパにくるんで返してくれる静的メソッドが用意されている。
通常のファイル入出力を複数のスレッドからおこなう場合、こうした機能を利用する。
using (var writer = new StreamWriter(....))
using (var synchronized = TextWriter.Synchronized(writer))
{
Parallel.For(0, 10000, i => synchronized.WriteLine(i));
}
では、Consoleクラスを使った
Console.Out.Write(...), WriteLine(...)
Console.Error.Write(...), WriteLine(...)
Console.In.Read(), ReadLine()
Console.Write(...), WriteLine(...)
Console.Read(), ReadLine()
はどうだろうか。
標準入力や標準出力などに用意されているオブジェクトについて、プログラマが個別に排他制御をおこなう必要があるだろうか。
Console.In/Out/Error
経由の入出力
通常の場合、Console.In
は標準入力を、Console.Out
は標準出力を、Console.Error
は標準エラー出力を表す。
Console
クラスにはSetIn(TextReader)
, SetOut(TextWriter)
, SetError(TextWriter)
という静的メソッドが用意されており、In
やOut
,Error
プロパティの中身を入れ替えることができる。
MSDNによれば、
By default, the value of the In property is a System.IO.TextReader object that represents the keyboard, and the values of the Out and Error properties are System.IO.TextWriter objects that represent a console window. However, you can set these properties to streams that do not represent the console window or keyboard; for example, you can set these properties to streams that represent files. To redirect the standard input, standard output, or standard error stream, call the Console.SetIn, Console.SetOut, or Console.SetError method, respectively. I/O operations that use these streams are synchronized, which means that multiple threads can read from, or write to, the streams. This means that methods that are ordinarily asynchronous, such as TextReader.ReadLineAsync, execute synchronously if the object represents a console stream.
と、In
, Out
, Error
といったプロパティ経由の入出力が同期されることが書かれている。
ソースをあたると、これらのプロパティに最初にアクセスしたときに、標準入力や標準出力をTextReader.Synchronized(TextReader)
静的メソッドやTextWriter.Synchronized(TextWriter)
静的メソッドに渡し、排他制御用のラッパにくるんだものを返すようになっている。
また、Console.SetIn(TextReader)
, Console.SetOut(TextWriter)
, Console.SetError(TextWriter)
の内部でもやはり、渡されたTextReader
オブジェクトあるいはTextWriter
オブジェクトを排他制御用のラッパにくるんでから格納するようになっている。
よって、Console.In
, Console.Out
, Console.Error
経由の入出力にわざわざ排他制御を付け足す必要はない。
なお、
Console.SetOut(TextWriter.Synchronized(mywriter));
のように、わざわざ排他制御でラッピングされたTextWriter
を渡してあげても、二重にラッパが適用されることはない。TextReader.Synchronized(TextReader)
静的メソッドやTextWriter.Synchronized(TextWriter)
静的メソッドは排他制御用のラッパとしてSyncTextWriter
/SyncTextReader
クラスを使用するが、このクラスのオブジェクトを渡されたときに限り、渡されたオブジェクトをそのまま返すようになっている。
Console.Read()
やConsole.Write(...)
での入出力
Console.Read()
Console.ReadLine()
はConsole.In.Read()
, Console.In.ReadLine()
のショートカットに過ぎない。同様に、
Console.Write(...)
Console.WriteLine(...)
はConsole.Out.Write(...)
, Console.Out.WriteLine(...)
のショートカットに過ぎない。
そのため、Console.Read()
やConsole.Write(...)
での入出力にも、わざわざ排他制御を付け足す必要はない。
結論
Consoleクラスを使った入出力には、プログラマが自分でスレッド間の排他制御をおこなう必要はない。.NETの機能にまかせよう。
参考
http://referencesource.microsoft.com/
https://msdn.microsoft.com/ja-jp/library/system.console(v=vs.110).aspx
https://stackoverflow.com/questions/4812508/is-console-writeline-thread-safe