我有一个名为List<string>
的{{1}}集合。
我有两个主题。 一个线程枚举所有列表元素并添加到集合。 第二个线程是枚举所有列表元素并从中删除。
如何使其线程安全? 我尝试创建全局对象“MyLock”并在每个线程函数中使用lock(MyLock)块但它不起作用。
你能帮助我吗?
答案 0 :(得分:3)
如果您可以访问.NET 4.0,则可以使用类ConcurrentQueue或BlockingCollection并使用ConcurrentQueue支持它。它完全符合您的要求并且不需要任何锁定。如果列表中没有可用的项目,BlockingCollection将使您的线程等待。
从ConcurrentQueue中删除您执行类似
的操作的示例ConcurrentQueue<MyClass> cq = new ConcurrentQueue<MyClass>();
void GetStuff()
{
MyClass item;
if(cq.TryDeqeue(out item))
{
//Work with item
}
}
这将尝试删除一个项目,但如果没有项目则不会执行任何操作。
BlockingCollection<MyClass> bc = BlockingCollection<MyClass>(new ConcurrentQueue<MyClass>());
void GetStuff()
{
if(!bc.IsCompleated) //check to see if CompleatedAdding() was called and the list is empty.
{
try
{
MyClass item = bc.Take();
//Work with item
}
catch (InvalidOpperationExecption)
{
//Take is marked as completed and is empty so there will be nothing to take
}
}
}
这将阻止并等待Take
,直到列表中有可用的内容。完成后,您可以调用CompleteAdding()
,当列表变空而不是阻止时,Take会抛出一个execption。
答案 1 :(得分:2)
如果不了解您的计划和要求,我会说这是一个“坏主意”。在迭代它的内容时改变List<>
很可能会引发异常。
最好使用Queue<>
代替List<>
,因为Queue<>
的设计考虑了同步。
答案 2 :(得分:0)
您应该可以直接锁定在列表中:
lock(list) {
//work with list here
}
但是,在枚举时从列表中添加/删除可能会导致异常...
答案 3 :(得分:0)
答案 4 :(得分:0)
您可以实现自己的IList<T>
版本,该版本包装基础List<T>
以在每次方法调用时提供锁定。
public class LockingList<T> : IList<T>
{
public LockingList(IList<T> inner)
{
this.Inner = inner;
}
private readonly object gate = new object();
public IList<T> Inner { get; private set; }
public int IndexOf(T item)
{
lock (gate)
{
return this.Inner.IndexOf(item);
}
}
public void Insert(int index, T item)
{
lock (gate)
{
this.Inner.Insert(index, item);
}
}
public void RemoveAt(int index)
{
lock (gate)
{
this.Inner.RemoveAt(index);
}
}
public T this[int index]
{
get
{
lock (gate)
{
return this.Inner[index];
}
}
set
{
lock (gate)
{
this.Inner[index] = value;
}
}
}
public void Add(T item)
{
lock (gate)
{
this.Inner.Add(item);
}
}
public void Clear()
{
lock (gate)
{
this.Inner.Clear();
}
}
public bool Contains(T item)
{
lock (gate)
{
return this.Inner.Contains(item);
}
}
public void CopyTo(T[] array, int arrayIndex)
{
lock (gate)
{
this.Inner.CopyTo(array, arrayIndex);
}
}
public int Count
{
get
{
lock (gate)
{
return this.Inner.Count;
}
}
}
public bool IsReadOnly
{
get
{
lock (gate)
{
return this.Inner.IsReadOnly;
}
}
}
public bool Remove(T item)
{
lock (gate)
{
return this.Inner.Remove(item);
}
}
public IEnumerator<T> GetEnumerator()
{
lock (gate)
{
return this.Inner.ToArray().AsEnumerable().GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
lock (gate)
{
return this.Inner.ToArray().GetEnumerator();
}
}
}
您可以像这样使用此代码:
var list = new LockingList<int>(new List<int>());
如果您使用大型列表和/或性能是一个问题,那么这种锁定可能不是非常高效,但在大多数情况下它应该没问题。
非常重要注意到两个GetEnumerator
方法调用.ToArray()
。这会在释放锁之前强制对枚举数进行评估,从而确保对列表的任何修改都不会影响实际的枚举。
使用lock (list) { ... }
或lock (list.SyncRoot) { ... }
等代码不会覆盖枚举期间发生的列表更改。这些解决方案仅涵盖对列表的并发修改 - 仅当所有调用者都在锁定内执行此操作时才会这样做。此外,如果一些讨厌的代码锁定并且不释放它们,这些解决方案可能会导致代码死亡。
在我的解决方案中,您会注意到我有一个object gate
,它是我锁定的类内部的私有变量。课外没有任何东西可以锁定,所以它是安全的。
我希望这会有所帮助。
答案 5 :(得分:0)
正如其他人已经说过的那样,您可以使用System.Collections.Concurrent
命名空间中的并发集合。如果您可以使用其中之一,则首选。
但如果你真的想要一个刚刚同步的列表,你可以查看SynchronizedCollection<T>
中的System.Collections.Generic
- 类。
请注意,您必须包含System.ServiceModel程序集,这也是我不喜欢它的原因。但有时我会用它。