C#并发列表问题

C#并发列表问题,c#,concurrency,C#,Concurrency,在C#中,我有一个简单类型的列表。此列表可由多个线程访问:可以添加或删除条目,并且可以检查条目是否存在。到目前为止,我已经将列表封装在一个对象中,只公开了这三个操作 我有几个案例要处理(与我刚才提到的方法不完全相同)。 线程只需检查条目是否存在。(简单) 线程可以检查条目是否存在,如果不存在,则添加该条目。 线程需要检查条目是否存在,如果存在,则等待删除该条目。 2和3的组合,其中线程检查条目是否存在,如果条目确实存在,则必须等到它被删除后才能添加它自己 整个想法是,条目的存在意味着锁。如果条目

在C#中,我有一个简单类型的列表。此列表可由多个线程访问:可以添加或删除条目,并且可以检查条目是否存在。到目前为止,我已经将列表封装在一个对象中,只公开了这三个操作

我有几个案例要处理(与我刚才提到的方法不完全相同)。

  • 线程只需检查条目是否存在。(简单)
  • 线程可以检查条目是否存在,如果不存在,则添加该条目。
  • 线程需要检查条目是否存在,如果存在,则等待删除该条目。
  • 2和3的组合,其中线程检查条目是否存在,如果条目确实存在,则必须等到它被删除后才能添加它自己
  • 整个想法是,条目的存在意味着锁。如果条目存在,则无法更改它标识的对象,并且代码无法继续,因为它正在其他地方被修改

    这些看似简单的新手情况,但我正在刷新自己的并发性问题,这让我有点偏执,而且我也不太熟悉C#的并发机制

    处理这个问题的最佳方法是什么?我完全疯了吗?是否应将检查和添加(测试和设置?)合并到第四个原子操作中?我会简单地在访问列表的方法中添加锁块吗


    另外,是否有可能对这类事情进行单元测试(而不是简单的操作、并发情况)?

    单元测试肯定很难

    这一切都可以通过.NET中的“本机”并发机制合理地完成:lock语句和
    Monitor.Wait
    /
    Monitor.pulsell
    。除非每个项目都有单独的监视器,否则每次删除任何内容时都需要唤醒所有线程,否则您将无法告诉“正确”的线程唤醒

    如果它实际上只是一组项目,那么您可能希望使用
    HashSet
    而不是
    List
    来表示集合,顺便说一句,您提到的与排序无关

    示例代码,假设一个集合适合您:

    using System;
    using System.Collections.Generic;
    using System.Threading;
    
    public class LockCollection<T>
    {
        private readonly HashSet<T> items = new HashSet<T>();
        private readonly object padlock = new object();
    
        public bool Contains(T item)
        {
            lock (padlock)
            {
                return items.Contains(item);
            }
        }
    
        public bool Add(T item)
        {
            lock (padlock)
            {
                // HashSet<T>.Add does what you want already :)
                // Note that it will return true if the item
                // *was* added (i.e. !Contains(item))
                return items.Add(item);
            }
        }
    
        public void WaitForNonExistence(T item)
        {
            lock (padlock)
            {
                while (items.Contains(item))
                {
                    Monitor.Wait(padlock);
                }
            }
        }
    
        public void WaitForAndAdd(T item)
        {
            lock (padlock)
            {
                WaitForNonExistence(item);
                items.Add(item);
            }
        }
    
        public void Remove(T item)
        {
            lock (padlock)
            {
                if (items.Remove(item))
                {
                    Monitor.PulseAll(padlock);
                }
            }
        }
    }
    
    使用系统;
    使用System.Collections.Generic;
    使用系统线程;
    公营锁收藏
    {
    private readonly HashSet items=new HashSet();
    私有只读对象挂锁=新对象();
    公共布尔包含(T项)
    {
    锁(挂锁)
    {
    返回项目。包含(项目);
    }
    }
    公共布尔添加(T项)
    {
    锁(挂锁)
    {
    //HashSet.Add已完成所需的操作:)
    //请注意,如果项目
    //*已添加(即!包含(项目))
    返回项目。添加(项目);
    }
    }
    公共无效等待不存在(T项)
    {
    锁(挂锁)
    {
    while(items.Contains(item))
    {
    监视器。等待(挂锁);
    }
    }
    }
    公共作废待处理数据(T项)
    {
    锁(挂锁)
    {
    等待不存在(项目);
    项目。添加(项目);
    }
    }
    公共作废删除(T项)
    {
    锁(挂锁)
    {
    如果(项目。移除(项目))
    {
    监视器。脉冲密封(挂锁);
    }
    }
    }
    }
    
    (无可否认,完全未经测试。您可能还想指定等待代码的超时…

    虽然#1可能是最简单的编写方法,但它本质上是一种无用的方法。除非您在完成“条目的存在性”的查询后持有相同的锁,否则您实际上返回的是“在过去某个时刻存在条目”。它不会给你任何关于当前条目存在的信息

    在发现列表中的某个值,然后执行任何检索、删除该值的操作之间,另一个线程可能会来为您删除该值


    如果检查为真/假,则并发列表上的包含操作应与您计划执行的操作相结合。例如TestAdd()或TestRemove()比Contains+Add或Contains+Remove安全得多有一种产品可以在单元测试中查找竞争条件等。它叫。不过,我不能说任何支持或反对其有效性的话。:)

    这是一个正确的、并发的、线程安全的、可并行的并发列表实现

    你应该在线程安全的收集界面上链接到你的博客文章。是的,但是#4就可以做到这一点。其他方法的可用性是值得怀疑的,但不一定是错误的。@Henk,我认为#2-#4是好方法#1虽然没有错,但只是没用。好吧,事情是这样的。在某些情况下,如果条目存在,我需要跳过一些代码并继续执行。@Matt,问题是你不能认为元素存在。你一定认为它是“曾经存在过的”+1,我找到了一个类似的解决方案,但哈希集使它更有效。Monitor类的演示很好,使用了嵌套锁等。唯一的问题可能是删除项时出现“踩踏”。不,该实现对于枚举来说是完全非线程安全的,过度锁定使其成为一个糟糕的选择。