ConcurrentBag <>から単一の特定のオブジェクトを削除する方法は?


109

ConcurrentBag<T>.NET 4 の新機能を使用して、特定の特定のオブジェクトをそこから削除するには、TryTake()TryPeek()利用できますか?

それを削除したくないTryTake()場合は、使用して結果のオブジェクトをリストに追加することを考えていますが、何か不足しているように感じます。これは正しい方法ですか?

回答:


89

簡単に言えば、簡単な方法でそれを行うことはできません。

ConcurrentBagは各スレッドのスレッドローカルキューを保持し、独自のキューが空になると、他のスレッドのキューのみを調べます。アイテムを削除して元に戻すと、次に削除するアイテムは同じアイテムになる可能性があります。アイテムを繰り返し削除して元に戻すと、すべてのアイテムを繰り返し処理できるという保証はありません。

あなたのための2つの選択肢:

  • すべてのアイテムを削除し、削除するアイテムが見つかるまで覚えてから、後で他のアイテムを元に戻します。2つのスレッドが同時にこれを実行しようとすると、問題が発生することに注意してください。
  • ConcurrentDictionaryなどのより適切なデータ構造を使用します。

9
SynchronizedCollectionも適切な代用となる可能性があります。
ILIA BROUDNO

2
@ILIABROUDNO-あなたはそれを答えにすべきです!辞書が不要な場合は、これは粗末なConcurrentDictionaryよりもはるかに優れています
Denis

2
ちなみに、SynchronizedCollectionは.NET Coreでは使用できません。このコメントの日付の時点で、System.Collections.Concurrent型は、.NET Coreベースの実装で現在行われている方法です。
マシュースナイダー

2
使用されている.NET Coreのバージョンはわかりませんが、.NET Core 2.1 SDKに基づくプロジェクトに取り組んでおり、SynchronizedCollectionはCollections.Generic名前空間で使用できます。
Lucas Leblanc

15

できません。そのバッグ、それは注文されていません。元に戻すと、無限ループに陥るだけです。

あなたはセットが欲しい。ConcurrentDictionaryでエミュレートできます。または、ロックで身を守るHashSet。


8
拡大してください。基になるConcurrentDictionaryのキーとして何を使用しますか?
Denise Skidmore

2
まあ、私はキーがあなたが保存しようとしているオブジェクトのタイプになると思います、そして値はある種のコレクションになるでしょう。HashSet彼が説明するように、それはa を「エミュレート」します。
Mathias Lykkegaard Lorenzen 2014

5

ConcurrentBagは、アイテムを追加して多くのスレッドから列挙できるリストを処理し、その名前が示唆しているように最終的にそれを破棄するのに最適です:)

Mark Byersが言ったように、削除したいアイテムを含まない新しいConcurrentBagを再構築できますが、ロックを使用してこれを複数のスレッドヒットから保護する必要があります。これはワンライナーです:

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

これは機能し、ConcurrentBagが設計された精神と一致します。


9
この答えは誤解を招くと思います。明確にするために、これは目的の削除操作でスレッドの安全性を提供しません。そして、その周りにロックをかけると、並行コレクションを使用する目的が少し損なわれます。
ILIA BROUDNO

1
同意する。さて、少しわかりやすくするために、ConcurrentBagは、完了時にコンテンツ全体を埋め、列挙し、破棄するように設計されています。アイテムを削除しようとする-私を含む-あらゆる試みは汚いハッキングをもたらします。少なくとも私は答えを出そうとしましたが、最善はConcurrentDictionaryのようなより良い並行コレクションクラスを使用することです。
Larry

4

マークは、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);
}

3

あなたが言うように、それTryTake()が唯一の選択肢です。これは、MSDNの例でもあります。Reflectorには、他に興味のある隠された内部メソッドもありません。


1
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.TryTakeを期待bag.Addし、FIFOの方法で機能します。コードはがをbag含むと想定しitem、で見つかるまでループitembagます。コードのみの回答はお勧めしません。解決策を説明する必要があります。
GDavid

-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を作成しているので、うまくいくとは思えませんが、よくわかりません。何かテスト?
shtse8 2018

3
いいえ、機能しません。それがどのように機能するか、新しい参照のみを作成します。古いオブジェクトはまだ古いオブジェクトを指しています。... "ref"を操作するとうまくいきます
Thomas Christof

-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;
}

-13

どうですか:

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

それはうまくいきます、私はどれほど効率的かわかりません...


これはバッグから何も取り除きません。取り出しているアイテムはバッグの中に残っています。
キース

3
「bag = new ConcurrentBag(bag.Where(x => x!= item))」である必要があります
atikot

4
@atikot、その行は私を笑わせました
parek 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.