线程安全的 List<T> 属性

183

我希望有一个可以在多线程下安全使用的 List<T> 属性的实现。

类似于这样:

private List<T> _list;

private List<T> MyT
{
    get { // return a copy of _list; }
    set { _list = value; }
}

看起来我仍然需要返回集合的副本,这样如果我们在遍历集合的同时对集合进行设置,则不会引发异常。

如何实现一个线程安全的集合属性?


8
使用锁,这样就可以了。 - atoMerz
可以使用线程安全的 IList<T> 实现(而不是 List<T>)吗? - Greg
3
你有检查过SynchronizedCollection<T>吗? - Roi Shabtai
1
使用BlockingCollection或ConcurrentDictionary。 - kumar chandraketu
这可能是所有优秀程序员在他们的生涯中迟早会问的问题。在所有List操作周围使用lock(...)似乎是正确的答案。 - thomasgalliker
显示剩余2条评论
15个回答

0

我相信 _list.ToList() 会复制一份列表。如果需要,您也可以查询它,例如:

_list.Select("query here").ToList(); 

无论如何,msdn说这确实是一份副本而不仅仅是一个引用。哦,对了,正如其他人指出的那样,您需要锁定set方法。


-1

看着原样例,人们可能会猜测意图是能够简单地用新的列表替换掉旧的列表。属性的设置器告诉了我们这一点。

微软的线程安全集合是用于安全地向集合中添加和删除项目的。但是,如果在应用程序逻辑中打算用新的集合替换旧的集合,我们可以再次猜测,列表的添加和删除功能是不需要的。

如果是这种情况,简单的答案就是使用IReadOnlyList接口:

 private IReadOnlyList<T> _readOnlyList = new List<T>();

    private IReadOnlyList<T> MyT
    {
       get { return _readOnlyList; }
       set { _readOnlyList = value; }
    }

在这种情况下,不需要使用任何锁定,因为没有修改集合的方式。如果在setter中,“_readOnlyList = value;”将被替换为更复杂的内容,则可能需要锁定。

-3

如果您想要安全地枚举,基本上需要使用锁。

请参考MSDN。http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

以下是MSDN的一部分,您可能会感兴趣:

此类型的公共静态成员(在Visual Basic中为Shared)是线程安全的。任何实例成员都不能保证是线程安全的。

列表可以支持多个读取器并发访问,只要集合没有被修改。枚举集合本质上不是一个线程安全的过程。在很少的情况下,当枚举与一个或多个写访问竞争时,确保线程安全的唯一方法是在整个枚举期间锁定集合。为了允许多个线程对集合进行读写访问,您必须实现自己的同步。


2
完全不是真的。你可以使用并发集合。 - ANeves

-4
这是一个无需锁定即可实现线程安全列表的类。
 public class ConcurrentList   
    {
        private long _i = 1;
        private ConcurrentDictionary<long, T> dict = new ConcurrentDictionary<long, T>();  
        public int Count()
        {
            return dict.Count;
        }
         public List<T> ToList()
         {
            return dict.Values.ToList();
         }

        public T this[int i]
        {
            get
            {
                long ii = dict.Keys.ToArray()[i];
                return dict[ii];
            }
        }
        public void Remove(T item)
        {
            T ov;
            var dicItem = dict.Where(c => c.Value.Equals(item)).FirstOrDefault();
            if (dicItem.Key > 0)
            {
                dict.TryRemove(dicItem.Key, out ov);
            }
            this.CheckReset();
        }
        public void RemoveAt(int i)
        {
            long v = dict.Keys.ToArray()[i];
            T ov;
            dict.TryRemove(v, out ov);
            this.CheckReset();
        }
        public void Add(T item)
        {
            dict.TryAdd(_i, item);
            _i++;
        }
        public IEnumerable<T> Where(Func<T, bool> p)
        {
            return dict.Values.Where(p);
        }
        public T FirstOrDefault(Func<T, bool> p)
        {
            return dict.Values.Where(p).FirstOrDefault();
        }
        public bool Any(Func<T, bool> p)
        {
            return dict.Values.Where(p).Count() > 0 ? true : false;
        }
        public void Clear()
        {
            dict.Clear();
        }
        private void CheckReset()
        {
            if (dict.Count == 0)
            {
                this.Reset();
            }
        }
        private void Reset()
        {
            _i = 1;
        }
    }

1
i++ 不是线程安全的。当你增加它时,你必须使用原子加,并且可能还要标记为 volatile。 CheckReset() 不是线程安全的。在条件检查和调用 Reset() 之间,任何事情都可能发生。 不要编写自己的多线程工具。 - Chris Rollins

-17
使用lock语句来实现。(点击此处了解更多信息。)
private List<T> _list;

private List<T> MyT
{
    get { return _list; }
    set
    {
        //Lock so only one thread can change the value at any given time.
        lock (_list)
        {
            _list = value;
        }
    }
}

请注意,这可能不完全是您要求的 - 您可能想在代码中更深层次地锁定,但我不能假设。请查看 lock 关键字,并根据您的具体情况调整其使用。

如果需要,您可以在 _list 变量中同时在 getset 块中进行 lock,这将使读/写不能同时发生。


2
这并不能解决他的问题;它只能阻止线程设置引用,而不能添加到列表中。 - Tejs
如果一个线程正在设置值,而另一个线程正在迭代集合(使用您的代码是可能的),那会怎么样? - Xaqron
就像我说的那样,锁定语句可能需要在代码中进一步移动。这只是使用锁定语句的示例。 - Josh M.
2
@Joel Mueller:当然,如果您制造了像那样的愚蠢示例。我只是试图说明提问者应该研究lock语句。使用类似的示例,我可以争辩说我们不应该使用for循环,因为你几乎可以毫不费力地使应用程序死锁:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ } - Josh M.
6
我从未声称你的代码会立即导致死锁。以下是为何该代码对于这个特定问题的回答不好的原因:1)它不能保护枚举列表期间或两个线程同时修改列表内容时的内容;2)仅锁定设置器而不锁定获取器意味着该属性实际上并不是线程安全的;3)锁定可以从类外访问的任何引用通常被认为是一种不良实践,因为它大大增加了意外出现死锁的可能性。这就是为什么lock (this)lock (typeof(this))都是大忌的原因。 - Joel Mueller
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接