LoginSignup
10
6

More than 5 years have passed since last update.

[.NET] Consoleクラスを使った入出力にはスレッド間排他制御は不要

Posted at

.NETにおけるファイル出力とマルチスレッド

複数のスレッドから、単一のTextWriterオブジェクトを使用してファイル出力しようとすると、通常例外が発生する。

exception.cs
using (var writer = new StreamWriter(...))
{
    Parallel.For(0, 10000, i => writer.WriteLine(i));
}

これを実行すると発生する例外(VS2017)
exception.png

この例外メッセージにあるとおり、.NETには

  • TextWriter.Synchronized(TextWriter)
  • TextReader.Synchronized(TextReader)

という、TextWriter/TextReaderを受け取って、排他制御をおこなうラッパにくるんで返してくれる静的メソッドが用意されている。
通常のファイル入出力を複数のスレッドからおこなう場合、こうした機能を利用する。

wrapped.cs
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)という静的メソッドが用意されており、InOut,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

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6