朝バタバタしてクリアじゃなかった... ちょっと間違ってましたこういうことです:
- 多数同時並行するタスクがある場合
- Windowsの場合はI/O完了ポートの「ブロックされたスレッドを検出するとワーカースレッドを世に放つ」機能があるので、同期APIがブロックしても、ワーカースレッド上限まではOK(ThreadPool.SetMaxThreadsの第二引数completionPortThreads)。
- Windowsではない環境では必ず上記機能が使えるとは言えない(多分単純スレッドプール)ので、同期APIによるスレッドブロックが発生すると、同時並行動作数はそれ以上伸びない。これを避けたい場合、Task.Run を使わざるを得ない。
- 多数同時並行したいタスクがない、GUIの場合
- 同期APIでブロックすると、それがUIスレッドの場合はUIが固まってしまうので、それを避けたいなら Task.Run を使わざるを得ない。但し、継続処理がUIを操作する場合はUIスレッドにマーシャリングすることになり、そのコストはかなり高いことに注意(≒await後の処理でUI部品を操作する場合など)。
- 多数同時並行したいタスクがない場合
- 同期APIと非同期APIを比較すると、非同期APIのほうが余分なハンドリングを必要とするコストがあるので、この場合は同期APIをそのまま使うのがいい。
Taskを使うかValueTaskを使うかによる差については、本来はすべてをTaskを返す非同期APIとして定義したいが、ほとんどの場合同期的に完了してしまう(つまり一瞬で操作が完了する、待機する可能性が0ではないが殆ど待機しないというような)処理を、いつもTaskで管理するのはコストが高いので、代わりにValueTaskを使って初めから完了しているものはコストをほとんど0とするようにするという話です。