すべての更新ループで1回書き込むリストがあります。次に、そのリストを読み取り、そのデータを操作するための複製を作成するいくつかのタスクを生成します。参照で使用するのではなく、新しいリストを作成するためのList.ToList()
呼び出しです。各タスクは自身のデータを操作し、その結果を生成します。現在、私は伝統的なロックの形式で使用しています
_lock(LockObject){
//write stuff
}
_
書くとき
_lock(LockObject){
localList=List.ToList();
}
_
読むとき。
現在の方法の代わりにReaderWriterLockSlim
オブジェクトを使用すると、速度の点でメリットがありますか?この場合、ToList()
メソッドは読み取りと見なされますか?
説明の編集
場合によります。読み取り操作が書き込み操作よりも著しく一般的である場合、クリティカルセクションではなくリーダー/ライターロックを使用すると効果的です。しかし、おそらくアプリケーションアーキテクチャを変更することで、さらに多くを達成できると思います。
書き込みがまれな場合は、コレクションを不変として扱い、変更時にまったく新しいリストを作成することで、パフォーマンスが大幅に向上する可能性があります。次に Interlocked.CompareExchange を使用して、スレッドセーフな方法でリストへの参照を更新できます。これにより、読者が防御的なコピーを作成する必要がなくなり(読み取りよりも書き込みの方が一般的である場合、これは大きな利益になるはずです)、ロックする必要がなくなります。
または、書き込みが読み取りよりも一般的である場合は、リストの代わりとしてConcurrentStackを評価する価値があるかもしれません。これは、ロックを必要とせずにコピーを作成するために使用できるアトミックToArrayメソッドを提供します。または、リストで順序が重要でない場合は、ConcurrentBagの方がパフォーマンスが良い場合があります。
明確化後に編集
ロックを完全になくすことができるので、リストを変更するときはリストをコピーする方がよいと私はまだ思います。読者が処理中にリストの内容を変更する必要がある場合でも、ストリーミングスタイルを使用するようにリファクタリングすることをお勧めします。 LINQ to Objects。これらの操作は実際にはデータを複製しません。既存のコードをこのスタイルに変換する方法の詳細については、Martin Fowler ループとコレクションパイプラインを使用したリファクタリング を参照してください。
それに失敗すると、リーダー/ライターのロックに切り替わる小さな利益が見られると思いますが、アプリケーションでロックの競合が異常に問題にならない限り、それは目立ちません。