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

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

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

これを実行すると発生する例外(VS2017)
![exception.png](https://qiita-image-store.s3.amazonaws.com/0/72443/a87a1499-f97d-dafb-bc8b-be5737ae8a8b.png)

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

- `TextWriter.Synchronized(TextWriter)`
- `TextReader.Synchronized(TextReader)`

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

```csharp: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)`という静的メソッドが用意されており、`In`や`Out`,`Error`プロパティの中身を入れ替えることができる。

[MSDN](https://msdn.microsoft.com/ja-jp/library/system.console(v=vs.110).aspx)によれば、

>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