线程和列表<>采集

时间:2011-10-24 22:54:41

标签: c# .net multithreading collections thread-safety

我有一个名为List<string>的{​​{1}}集合。

我有两个主题。 一个线程枚举所有列表元素并添加到集合。 第二个线程是枚举所有列表元素并从中删除。

如何使其线程安全? 我尝试创建全局对象“MyLock”并在每个线程函数中使用lock(MyLock)块但它不起作用。

你能帮助我吗?

6 个答案:

答案 0 :(得分:3)

如果您可以访问.NET 4.0,则可以使用类ConcurrentQueueBlockingCollection并使用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)

锁定SyncRoot的{​​{1}}:

List<T>

有关如何正确使用它的更多信息,请参见here

答案 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程序集,这也是我不喜欢它的原因。但有时我会用它。