列挙中に変更できるスレッドセーフなコレクションを作成したいと考えています。
サンプルActionSet
クラスにはAction
ハンドラが格納されています。Add
リストに新しいハンドラーを追加するメソッドInvoke
と、収集されたすべてのアクション ハンドラーを列挙して呼び出すメソッドがあります。意図された作業シナリオには、列挙中に時折変更される非常に頻繁な列挙が含まれます。
Add
列挙が終了していないときにメソッドを使用して変更すると、通常のコレクションは例外をスローします。
この問題には、簡単ではあるが時間のかかる解決策があります。列挙する前にコレクションを複製するだけです。
class ThreadSafeSlowActionSet {
List<Action> _actions = new List<Action>();
public void Add(Action action) {
lock(_actions) {
_actions.Add(action);
}
}
public void Invoke() {
lock(_actions) {
List<Action> actionsClone = _actions.ToList();
}
foreach (var action in actionsClone ) {
action();
}
}
}
このソリューションの問題は、列挙のオーバーヘッドであり、列挙を非常に高速にしたいと考えています。
列挙中でも新しい値を追加できる、かなり高速な「再帰セーフ」なコレクションを作成しました。メイン コレクションの列挙中に新しい値を追加すると、その値はメイン コレクションではなく_actions
一時_delta
コレクションに追加されます。すべての列挙が終了すると、_delta
値が_actions
コレクションに追加されます。_actions
メインコレクションの列挙 (コレクションの作成)中にいくつかの新しい値を追加し_delta
、Invoke メソッドを再度入力する場合は、新しいマージされたコレクション ( _actions
+ _delta
) を作成し、それで置き換える_actions
必要があります。
したがって、このコレクションは「再帰セーフ」に見えますが、スレッドセーフにしたいと考えています。Interlocked.*
このコレクションをスレッド セーフにするには、コンストラクト、クラス フロム、およびその他の同期プリミティブを使用する必要があると思いますが、System.Threading
その方法がよくわかりません。
このコレクションをスレッドセーフにする方法は?
class RecursionSafeFastActionSet {
List<Action> _actions = new List<Action>(); //The main store
List<Action> _delta; //Temporary buffer for storing added values while the main store is being enumerated
int _lock = 0; //The number of concurrent Invoke enumerations
public void Add(Action action) {
if (_lock == 0) { //_actions list is not being enumerated and can be modified
_actions.Add(action);
} else { //_actions list is being enumerated and cannot be modified
if (_delta == null) {
_delta = new List<Action>();
}
_delta.Add(action); //Storing the new values in the _delta buffer
}
}
public void Invoke() {
if (_delta != null) { //Re-entering Invoke after calling Add: Invoke->Add,Invoke
Debug.Assert(_lock > 0);
var newActions = new List<Action>(_actions); //Creating a new list for merging delta
newActions.AddRange(_delta); //Merging the delta
_delta = null;
_actions = newActions; //Replacing the original list (which is still being iterated)
}
_lock++;
foreach (var action in _actions) {
action();
}
_lock--;
if (_lock == 0 && _delta != null) {
_actions.AddRange(_delta); //Merging the delta
_delta = null;
}
}
}
更新ThreadSafeSlowActionSet
:バリアントを追加しました。