【问题标题】:How to remove a single, specific object from a ConcurrentBag<>?如何从 ConcurrentBag<> 中删除单个特定对象?
【发布时间】:2011-03-03 01:41:20
【问题描述】:

使用 .NET 4 中的新 ConcurrentBag&lt;T&gt;,当只有 TryTake()TryPeek() 可用时,如何从中删除某个特定对象?

我正在考虑使用TryTake(),然后如果我不想想要将其删除,则只需将结果对象添加回列表中,但我觉得我可能会遗漏一些东西。这是正确的方法吗?

【问题讨论】:

    标签: c# c#-4.0


    【解决方案1】:

    这是我在项目中使用的扩展类。它可以从 ConcurrentBag 中删除单个项目,也可以从包中删除项目列表

    public static class ConcurrentBag
    {
        static Object locker = new object();
    
        public static void Clear<T>(this ConcurrentBag<T> bag)
        {
            bag = new ConcurrentBag<T>();
        }
    
    
        public static void Remove<T>(this ConcurrentBag<T> bag, List<T> itemlist)
        {
            try
            {
                lock (locker)
                {
                    List<T> removelist = bag.ToList();
    
                    Parallel.ForEach(itemlist, currentitem => {
                        removelist.Remove(currentitem);
                    });
    
                    bag = new ConcurrentBag<T>();
    
    
                    Parallel.ForEach(removelist, currentitem =>
                    {
                        bag.Add(currentitem);
                    });
                }
    
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }
    
        public static void Remove<T>(this ConcurrentBag<T> bag, T removeitem)
        {
            try
            {
                lock (locker)
                {
                    List<T> removelist = bag.ToList();
                    removelist.Remove(removeitem);                
    
                    bag = new ConcurrentBag<T>();
    
                    Parallel.ForEach(removelist, currentitem =>
                    {
                        bag.Add(currentitem);
                    });
                }
    
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }
    }
    

    【讨论】:

    • 很难相信它可以工作,因为您正在局部变量中创建新的 ConcurrentBag,但我不确定。有什么测试吗?
    • 不,它不起作用。它应该如何工作,您正在创建一个唯一的新参考。旧的仍然指向旧对象....如果您使用“ref”,它将起作用
    【解决方案2】:
    public static void Remove<T>(this ConcurrentBag<T> bag, T item)
    {
        while (bag.Count > 0)
        {
            T result;
            bag.TryTake(out result);
    
            if (result.Equals(item))
            {
                break; 
            }
    
            bag.Add(result);
        }
    
    }
    

    【讨论】:

    • ConcurrentBag 是一个无序集合,但您的代码期望bag.TryTakebag.Add 以先进先出的方式工作。您的代码假定bag 包含item,它会循环直到在bag 中找到item。不鼓励仅使用代码的答案,您应该解释您的解决方案。
    • 在您的示例中,在某些情况下,集合中没有其他线程需要的项目。
    【解决方案3】:

    Mark 是正确的,ConcurrentDictionary 将以您想要的方式工作。如果您仍想使用ConcurrentBag,请注意以下效率低下的问题,将助您一臂之力。

    var stringToMatch = "test";
    var temp = new List<string>();
    var x = new ConcurrentBag<string>();
    for (int i = 0; i < 10; i++)
    {
        x.Add(string.Format("adding{0}", i));
    }
    string y;
    while (!x.IsEmpty)
    {
        x.TryTake(out y);
        if(string.Equals(y, stringToMatch, StringComparison.CurrentCultureIgnoreCase))
        {
             break;
        }
        temp.Add(y);
    }
    foreach (var item in temp)
    {
         x.Add(item);
    }
    

    【讨论】:

      【解决方案4】:

      ConcurrentBag 非常适合处理 List,您可以在其中添加项目并从多个线程枚举,然后最终将其丢弃,正如其名称所暗示的那样:)

      As Mark Byers told,您可以重新构建一个新的 ConcurrentBag,其中不包含您要删除的项目,但您必须使用锁来保护它免受多个线程的影响。这是一个单行:

      myBag = new ConcurrentBag<Entry>(myBag.Except(new[] { removedEntry }));
      

      这很有效,并且符合 ConcurrentBag 的设计精神。

      【讨论】:

      • 我觉得这个答案具有误导性。需要明确的是,这不会在所需的 Remove 操作中提供任何线程安全性。并且在它周围加锁有点违背了使用并发集合的目的。
      • 我同意。好吧,澄清一下,ConcurrentBag 的设计目的是在完成后将其全部内容填充、枚举和丢弃。任何移除物品的尝试(包括我的尝试)都将导致恶意攻击。至少我试图提供一个答案,虽然最好是使用更好的并发集合类,比如 ConcurrentDictionary。
      【解决方案5】:
      public static ConcurrentBag<String> RemoveItemFromConcurrentBag(ConcurrentBag<String> Array, String Item)
      {
          var Temp=new ConcurrentBag<String>();
          Parallel.ForEach(Array, Line => 
          {
             if (Line != Item) Temp.Add(Line);
          });
          return Temp;
      }
      

      【讨论】:

        【解决方案6】:

        怎么样:

        bag.Where(x => x == item).Take(1);
        

        它有效,我不确定效率如何......

        【讨论】:

        • 这不会从包中取出任何东西。您取回的物品仍留在包内。
        • 应该是“bag = new ConcurrentBag(bag.Where(x => x != item))”
        • @atikot,那句话让我笑了
        【解决方案7】:

        你不能。它是一个袋子,它没有被订购。当你把它放回去时,你只会陷入无限循环。

        你想要一套。您可以使用 ConcurrentDictionary 模拟一个。或者一个用锁保护自己的 HashSet。

        【讨论】:

        • 请展开。您将使用什么作为底层 ConcurrentDictionary 中的键?
        • 好吧,我假设键是您要存储的对象的类型,然后值是某种集合。正如他所描述的那样,这将“模仿”HashSet
        【解决方案8】:

        正如您提到的,TryTake() 是唯一的选择。这也是MSDN 上的示例。 Reflector 也没有显示其他隐藏的感兴趣的内部方法。

        【讨论】:

          【解决方案9】:

          简短的回答:你不能简单地做到这一点。

          ConcurrentBag 为每个线程保留一个线程本地队列,并且只有在它自己的队列为空时才会查看其他线程的队列。如果您移除一个项目并将其放回原处,那么您移除的下一个项目可能又是同一个项目。不能保证反复删除项目并将它们放回原处将允许您迭代所有项目。

          两种选择:

          • 删除所有项目并记住它们,直到找到要删除的项目,然后再将其他项目放回原处。请注意,如果两个线程同时尝试执行此操作,您将遇到问题。
          • 使用更合适的数据结构,例如ConcurrentDictionary

          【讨论】:

          • SynchronizedCollection 也可能是一个合适的替代品。
          • @ILIABROUDNO - 你应该把它作为答案!当您不需要字典时,这比杂乱无章的 ConcurrentDictionary 要好得多
          • 仅供参考,SynchronizedCollection 在 .NET Core 中不可用。截至本评论发布之日,System.Collections.Concurrent 类型是当前基于 .NET Core 的实现方式。
          • 我不确定正在使用哪个版本的 .NET Core,但我正在开发一个基于 .NET Core 2.1 SDK 的项目,并且 SynchronizedCollection 现在在 Collections.Generic 命名空间中可用.
          猜你喜欢
          • 2014-05-19
          • 1970-01-01
          • 2017-06-22
          • 2011-07-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-21
          • 2019-04-18
          相关资源
          最近更新 更多