Ark*_*kun 6 .net c# collections concurrency thread-safety
我想创建一个可以在枚举时修改的线程安全集合.
示例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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
这个解决方案的问题是枚举开销,我希望枚举非常快.
我创建了一个相当快速的"递归安全"集合,允许在枚举时添加新值.如果_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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
更新:添加了ThreadSafeSlowActionSet变体.
这是为线程安全而修改的类:
class SafeActionSet
{
Object _sync = new Object();
List<Action> _actions = new List<Action>(); //The main store
List<Action> _delta = new List<Action>(); //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)
{
lock(sync)
{
if (0 == _lock)
{ //_actions list is not being enumerated and can be modified
_actions.Add(action);
}
else
{ //_actions list is being enumerated and cannot be modified
_delta.Add(action); //Storing the new values in the _delta buffer
}
}
}
public void Invoke()
{
lock(sync)
{
if (0 < _delta.Count)
{ //Re-entering Invoke after calling Add: Invoke->Add,Invoke
Debug.Assert(0 < _lock);
var newActions = new List<Action>(_actions); //Creating a new list for merging delta
newActions.AddRange(_delta); //Merging the delta
_delta.Clear();
_actions = newActions; //Replacing the original list (which is still being iterated)
}
++_lock;
}
foreach (var action in _actions)
{
action();
}
lock(sync)
{
--_lock;
if ((0 == _lock) && (0 < _delta.Count))
{
_actions.AddRange(_delta); //Merging the delta
_delta.Clear();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
出于以下原因,我做了一些其他调整: