我有public static List<MyDoggie> DoggieList;
DoggieList
被多个进程追加并写入。
我们经常遇到这个例外:
收藏被修改;枚举操作可能无法执行
假设有多个类写入DoggieList
,我们如何解决此异常?
请注意,此设计不是很好,但此时我们需要在生产中快速修复它。
我们如何安全地从多个线程对此列表执行突变?
我知道我们可以做类似的事情:
lock(lockObject)
{
DoggieList.AddRange(...)
}
但是,我们可以针对相同的DoggieList
从多个班级执行此操作吗?
答案 0 :(得分:1)
使用lock
防止并发读数的缺点。
不需要更改集合类型的有效解决方案是使用ReaderWriterLockSlim
private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
使用以下扩展方法:
public static class ReaderWriterLockSlimExtensions
{
public static void ExecuteWrite(this ReaderWriterLockSlim aLock, Action action)
{
aLock.EnterWriteLock();
try
{
action();
}
finally
{
aLock.ExitWriteLock();
}
}
public static void ExecuteRead(this ReaderWriterLockSlim aLock, Action action)
{
aLock.EnterReadLock();
try
{
action();
}
finally
{
aLock.ExitReadLock();
}
}
}
可以通过以下方式使用:
_lock.ExecuteWrite(() => DoggieList.Add(new Doggie()));
_lock.ExecuteRead(() =>
{
// safe iteration
foreach (MyDoggie item in DoggieList)
{
....
}
})
最后,如果您想基于此构建自己的集合:
public class SafeList<T>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly List<T> _list = new List<T>();
public T this[int index]
{
get
{
T result = default(T);
_lock.ExecuteRead(() => result = _list[index]);
return result;
}
}
public List<T> GetAll()
{
List<T> result = null;
_lock.ExecuteRead(() => result = _list.ToList());
return result;
}
public void ForEach(Action<T> action) =>
_lock.ExecuteRead(() => _list.ForEach(action));
public void Add(T item) => _lock.ExecuteWrite(() => _list.Add(item));
public void AddRange(IEnumerable<T> items) =>
_lock.ExecuteWrite(() => _list.AddRange(items));
}
这个列表是完全安全的,多个线程可以并行添加或获取项目而不会出现任何并发问题。此外,多个线程可以并行获取项目而不会相互锁定,只有在写入时才会有1个单线程可以处理集合。
请注意,此集合未实现IEnumerable<T>
,因为您可能会获得一个枚举器并忘记处理它,这会使列表锁定在读取模式。
答案 1 :(得分:1)
你也可以创建自己的类并封装锁定的东西,你可以尝试如下,
你可以添加你想要的方法,如addRange,Remove等。
class MyList {
private object objLock = new object();
private List<int> list = new List<int>();
public void Add(int value) {
lock (objLock) {
list.Add(value);
}
}
public int Get(int index) {
int val = -1;
lock (objLock) {
val = list[0];
}
return val;
}
public void GetAll() {
List<int> retList = new List<int>();
lock (objLock) {
retList = new List<T>(list);
}
return retList;
}
}
好东西:并发收集非常详细:http://www.albahari.com/threading/part5.aspx#_Concurrent_Collections
利用并发集合ConcurrentBag类也可以解决与多线程更新相关的问题
示例
using System.Collections.Concurrent;
using System.Threading.Tasks;
public static class Program
{
public static void Main()
{
var items = new[] { "item1", "item2", "item3" };
var bag = new ConcurrentBag<string>();
Parallel.ForEach(items, bag.Add);
}
}
答案 2 :(得分:0)
制作DoggieList
类型的ConcurrentStack
,然后使用pushRange
方法。它是线程安全的。
using System.Collections.Concurrent;
var doggieList = new ConcurrentStack<MyDoggie>();
doggieList.PushRange(YourCode)