如何创建线程安全的通用列表?

The*_*ght 23 c# generics multithreading list

我有一个通用列表如下

public static readonly List<Customer> Customers = new List<Customer>();
Run Code Online (Sandbox Code Playgroud)

我正在使用以下方法:

.Add
.Find
.FirstOrDefault
Run Code Online (Sandbox Code Playgroud)

最后2个是LINQ扩展.

我需要使这个线程安全的能够运行容器类的多个实例.

怎么实现呢?

Jar*_*Par 42

如果这些是您正在使用的唯一函数,List<T>那么最简单的方法是编写一个快速包装器,将访问与a同步lock

class MyList<T> { 
  private List<T> _list = new List<T>();
  private object _sync = new object();
  public void Add(T value) {
    lock (_sync) {
      _list.Add(value);
    }
  }
  public bool Find(Predicate<T> predicate) {
    lock (_sync) {
      return _list.Find(predicate);
    }
  }
  public T FirstOrDefault() {
    lock (_sync) {
      return _list.FirstOrDefault();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我强烈推荐新类型+私有锁对象的方法.对于继承你的代码的下一个人来说,实际意图是什么让它变得更加明显.

另请注意,.Net 4.0引入了一组新的集合,专门用于从多个线程中使用.如果其中一个满足您的需求,我强烈建议您使用它自己滚动.

  • ConcurrentStack<T>
  • ConcurrentQueue<T>


Alb*_*reo 10

如果您使用的是.NET Framework 4或更高版本,则可以使用线程安全集合.

您可以替换List<T>ConcurrentBag<T>:

namespace Playground.Sandbox
{
    using System.Collections.Concurrent;
    using System.Threading.Tasks;

    public static class Program
    {
        public static void Main()
        {
            var items = new[] { "Foo", "Bar", "Baz" };
            var bag = new ConcurrentBag<string>();
            Parallel.ForEach(items, bag.Add);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 行李的行为与订单列表不同. (5认同)
  • 并发袋非常有用,直到您需要删除东西为止。 (3认同)
  • 没有使用SynchronizedCollection类有什么共鸣吗? (2认同)

Jas*_*onS 7

要扩展@ JaradPar的答案,这里是一个完整的实现,具有一些额外的功能,如摘要中所述

    /// <summary>
/// a thread-safe list with support for:
/// 1) negative indexes (read from end).  "myList[-1]" gets the last value
/// 2) modification while enumerating: enumerates a copy of the collection.
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class ConcurrentList<TValue> : IList<TValue>
{
    private object _lock = new object();
    private List<TValue> _storage = new List<TValue>();
    /// <summary>
    /// support for negative indexes (read from end).  "myList[-1]" gets the last value
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    public TValue this[int index]
    {
        get
        {
            lock (_lock)
            {
                if (index < 0)
                {
                    index = this.Count - index;
                }
                return _storage[index];
            }
        }
        set
        {
            lock (_lock)
            {
                if (index < 0)
                {
                    index = this.Count - index;
                }
                _storage[index] = value;
            }
        }
    }

    public void Sort()
    {
        lock (_lock)
        {
            _storage.Sort();
        }
    }

    public int Count
    {
        get
        {
            return _storage.Count;
        }
    }

    bool ICollection<TValue>.IsReadOnly
    {
        get
        {
            return ((IList<TValue>)_storage).IsReadOnly;
        }
    }

    public void Add(TValue item)
    {
        lock (_lock)
        {
            _storage.Add(item);
        }
    }

    public void Clear()
    {
        lock (_lock)
        {
            _storage.Clear();
        }
    }

    public bool Contains(TValue item)
    {
        lock (_lock)
        {
            return _storage.Contains(item);
        }
    }

    public void CopyTo(TValue[] array, int arrayIndex)
    {
        lock (_lock)
        {
            _storage.CopyTo(array, arrayIndex);
        }
    }


    public int IndexOf(TValue item)
    {
        lock (_lock)
        {
            return _storage.IndexOf(item);
        }
    }

    public void Insert(int index, TValue item)
    {
        lock (_lock)
        {
            _storage.Insert(index, item);
        }
    }

    public bool Remove(TValue item)
    {
        lock (_lock)
        {
            return _storage.Remove(item);
        }
    }

    public void RemoveAt(int index)
    {
        lock (_lock)
        {
            _storage.RemoveAt(index);
        }
    }

    public IEnumerator<TValue> GetEnumerator()
    {

        lock (_lock)
        {
            lock (_lock)
            {
                return (IEnumerator<TValue>)_storage.ToArray().GetEnumerator();
            }
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 只是一个问题...为什么在公共IEnumerator &lt;TValue&gt; GetEnumerator()中双重锁定?谢谢。 (2认同)
  • 嗨@JasonS只是一个单挑我不得不改变公共```IEnumerator <TValue> GetEnumerator()```,在锁内,```return _storage.GetEnumerator();```.我得到一个奇怪的无法转换类型'SZArrayEnumerator'要键入消息的对象.谢谢! (2认同)
  • @JasonS 如果您不介意的话,我对“GetEnumerator()”方法进行了改进(删除了双“锁”)。我还为“Count”属性添加了“lock”保护,因为安全总比后悔好! (2认同)