我想创建一个可以同时列举修改的线程安全的集合。
I want to create a thread-safe collection that can be modified while being enumerated.
样品 ActionSet
类存储动作
处理程序。它具有添加
方法,增加了一个新的处理程序列表和调用
方法,列举并调用所有的收集到的动作处理程序。预期的工作方案包括非常频繁枚举偶尔修改,同时列举。
The sample ActionSet
class stores Action
handlers. It has the Add
method that adds a new handler to the list and the Invoke
method that enumerates and invokes all of the collected action handlers. The intended working scenarios include very frequent enumerations with occasional modifications while enumerating.
普通收藏抛出异常,如果您使用添加
方法,而枚举还没有结束对其进行修改。
Normal collections throw exception if you modify them using the Add
method while the enumeration is not over.
有一个简单的,但速度缓慢问题的解决方法:只要克隆收集统计前:
There is an easy, but slow solution to the problem: Just clone the collection before enumeration:
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();
}
}
}
使用此解决方案的问题是枚举开销和我想枚举来非常快。
The problem with this solution is the enumeration overhead and I want enumeration to be very fast.
我已经创建了一个相当快的递归安全的集合,它允许添加即使枚举新值。如果您在主 _actions
集合被枚举添加新值,该值被添加到临时 _delta 集合,而不是的主要原因之一。毕竟枚举完成后,
_delta 值将被添加到
_actions
集合。如果你添加了一些新的价值,而主要的 _actions
集合被枚举(创建 _delta 集合),然后重新再次输入Invoke方法,我们需要创建一个新的合并集合(
_actions
+ _delta )和替换
_actions
吧。
I've created a rather fast "recursion-safe" collection that allows adding new values even while enumerating. If you add new values while the main _actions
collection is being enumerated, the values are added to the temporary _delta
collection instead of the main one. After all enumerations are finished, the _delta
values are added to the _actions
collection. If you add some new values while the main _actions
collection is being enumerated (creating the _delta
collection) and then re-enter the Invoke method again we have to create a new merged collection (_actions
+ _delta
) and replace _actions
with it.
那么,这个集合是递归安全的,但我想让它线程安全的。我想,我需要使用互锁。*
结构,由的System.Threading
类和其他同步原语,使这个集合线程安全的,但我没有对如何做一个好主意。
So, this collection looks "recursion-safe", but I want to make it thread-safe. I think that I need to use the Interlocked.*
constructs, classes from System.Threading
and other synchronization primitives to make this collection thread-safe, but I don't have a good idea on how to do that.
如何使这个集合线程安全的?
How to make this collection thread-safe?
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
变
下面是你们班改装的线程安全的:
Here is your class modified for thread safety:
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();
}
}
}
}
我做了一些其他的调整,由于以下原因:
I made a few other tweaks, for the following reason:
颠倒IF EX pressions先有恒定值,所以如果我做了 错字和把=,而不是==或!=等,编译器会 立即告诉错字我。 (:一种习惯我钻进了,因为我的大脑和手指往往是不同步的:) preallocated _delta,并称为 .Clear(),而不是将其设置为null, 因为我觉得这样更易于阅读。 各种锁(_sync){...} 给你的所有的实例变量访问你的线程安全。 :(除您访问_action枚举本身): reversed IF expressions to have constant value first, so if I do a typo and put "=" instead of "==" or "!=" etc., the compiler will instantly tell me of the typo. (: a habit I got into because my brain and fingers are often out of sync :) preallocated _delta, and called .Clear() instead of setting it to null, because I find it is easier to read. the various lock(_sync) {...} give you your thread safety on all instance variable access. :( with the exception of your access to _action in the enumeration itself. ):上一篇:如何以异步方式下asp.net运行持久的过程?持久、过程、方式、net
下一篇:在.NET并发BlockingCollection有内存泄漏?内存、NET、BlockingCollection