【问题标题】:Threading and List<> collection线程和 List<> 集合
【发布时间】:2011-12-14 12:54:19
【问题描述】:

我有一个名为 List&lt;string&gt; listList&lt;string&gt; 集合。

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

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

你能帮帮我吗?

【问题讨论】:

  • 您应该使用线程安全的生产者-消费者集合。
  • 发布您的代码。锁定全局对象应该可以工作,所以听起来其他地方有错误。

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


【解决方案1】:

如果您有权访问 .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。

【讨论】:

    【解决方案2】:

    在不了解您的程序和要求的情况下,我会说这是一个“坏主意”。在迭代 List&lt;&gt; 的内容时更改 List&lt;&gt; 很可能会引发异常。

    您最好使用Queue&lt;&gt; 而不是List&lt;&gt;,因为Queue&lt;&gt; 的设计考虑了同步。

    【讨论】:

    【解决方案3】:

    你应该可以直接锁定你的列表:

    lock(list) {
        //work with list here
    }
    

    但是在枚举列表时添加/删除可能会导致异常...

    【讨论】:

      【解决方案4】:

      锁定SyncRootList&lt;T&gt;

      lock(list.SyncRoot)
      {
      
      }
      

      更多正确使用方法请见here

      【讨论】:

        【解决方案5】:

        您可以实现自己的IList&lt;T&gt; 版本,它封装了底层List&lt;T&gt; 以提供对每个方法调用的锁定。

        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,它是我锁定的类的内部私有变量。课堂外没有任何东西可以锁定它,所以它是安全的。

        我希望这会有所帮助。

        【讨论】:

          【解决方案6】:

          正如其他人已经说过的,您可以使用来自 System.Collections.Concurrent 命名空间的并发集合。如果您可以使用其中之一,这是首选。

          但如果你真的想要一个刚刚同步的列表,你可以查看System.Collections.Generic 中的SynchronizedCollection&lt;T&gt;-Class。

          请注意,您必须包含 System.ServiceModel 程序集,这也是我不太喜欢它的原因。但有时我会使用它。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-08-20
            • 2016-12-11
            • 1970-01-01
            • 2023-03-11
            • 1970-01-01
            • 1970-01-01
            • 2013-04-02
            • 2011-10-07
            相关资源
            最近更新 更多