自作ゲームで木構造のシーングラフを持っています。
親ノードは複数の子ノードを持ちゲームの進行に応じてノードが追加されたり削除されたりします。ようはコレクションの変更です。async/await を使った非同期プログラミングでこれを行うとタイミングによっては foreach でぐるぐる回している最中に別スレッドからコレクションを変更することになり例外が発生します(これ自体は当然の動作)。

質問は非同期プログラミングでのコレクション操作のベストな方法を教えて下さい、という事です。

  1. SemaphoreSlim で排他制御を行う
  2. コレクションを必ず ToArray() でコピーしてから使う
  3. 追加および削除を1つのスレッドで行うようにロジックを変更する

どれも一長一短で決めかねています。ご意見をお聞かせ下さい。


回答を考慮して追加

  1. System.Collections.Concurrent 名前空間のスレッドセーフなコレクションを使用する
  2. System.Collections.ImmutableList 名前空間の不変なコレクションを使用する

4.はConcurrentBag, ConcurrentStack, ConcurrentQueue, ConcurrentDictionaryなど。既存のListに比べて呼び出し方法が大分異なる。基本はキューとスタックなのでProducer-Consumerパターン。
5.は.Net標準ではないがマイクロソフト提供のライブラリ。NuGetでインストール可能。「不変」(Immutable)なコレクションなのでマルチスレッド環境でも問題が起きない。コレクションの変更はコピーして変更を加えて差し替えるので比較的コストが高い。参照するだけなら問題はない。

排他制御がコードに現れない、既存のListとほぼ同様のインターフェースを持つ、書き換えはそんなに起きないので無視できる、などを評価して5番のImmutableListがベストですね。
今のところ.Net標準ではないですがそのうち標準として取り込まれるような気がします。これは便利だ。