ConcurrentBag <>에서 단일 특정 개체를 제거하는 방법은 무엇입니까?


109

새로운 함께 ConcurrentBag<T>.NET 4, 당신은 어떻게 할 때 단지 그것에서 어떤 특정 개체를 제거 할 TryTake()TryPeek()사용할 수 있습니까?

내가 사용하는 생각하고 TryTake()내가이라면 단지 목록에 결과 개체 다시 추가 하지 않는 그것을 제거 할,하지만 난 뭔가를 누락 될 수 있습니다 같은 느낌. 이것이 올바른 방법입니까?

답변:


89

짧은 대답 : 쉬운 방법으로는 할 수 없습니다.

ConcurrentBag는 각 스레드에 대한 스레드 로컬 큐를 유지하며 자신의 큐가 비워지면 다른 스레드의 큐만 확인합니다. 항목을 제거했다가 다시 넣으면 제거하는 다음 항목이 다시 동일한 항목이 될 수 있습니다. 항목을 반복적으로 제거했다가 다시 넣으면 모든 항목을 반복 할 수 있다는 보장은 없습니다.

두 가지 대안 :

  • 제거 할 항목을 찾을 때까지 모든 항목을 제거하고 기억 한 다음 나중에 다른 항목을 다시 넣으십시오. 두 스레드가 동시에이 작업을 시도하면 문제가 발생합니다.
  • ConcurrentDictionary 와 같은 더 적합한 데이터 구조를 사용하십시오 .

9
SynchronizedCollection이 적절한 대체물 일 수도 있습니다.
ILIA BROUDNO 2010 년

2
@ILIABROUDNO-대답으로 넣어야합니다! 당신이 사전이 필요하지 않을 때 즉 SO 훨씬 더 나은 kludgey ConcurrentDictionary보다
데니스

2
참고로 SynchronizedCollection은 .NET Core에서 사용할 수 없습니다. 이 주석의 날짜를 기준으로 System.Collections.Concurrent 형식은 .NET Core 기반 구현을위한 현재 방식입니다.
매튜 스나이더

2
어떤 버전의 .NET Core가 사용되었는지 확실하지 않지만 .NET Core 2.1 SDK를 기반으로하는 프로젝트에서 작업 중이며 SynchronizedCollection을 Collections.Generic 네임 스페이스에서 사용할 수 있습니다.
Lucas Leblanc 19 년

15

당신은 할 수 없습니다. 가방인데 주문되지 않았습니다. 다시 넣으면 끝없는 루프에 갇히게 될 것입니다.

당신은 세트를 원합니다. ConcurrentDictionary로 하나를 에뮬레이션 할 수 있습니다. 또는 잠금으로 자신을 보호하는 HashSet.


8
확장하십시오. 기본 ConcurrentDictionary에서 키로 무엇을 사용 하시겠습니까?
Denise Skidmore 2014 년

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

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

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.TryTakebag.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

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

작동하지만 얼마나 효율적인지 잘 모르겠습니다.


이것은 가방에서 아무것도 제거하지 않습니다. 검색중인 항목은 가방 안에 남아 있습니다.
Keith

3
"가방 새로운 ConcurrentBag (! bag.Where (X => X = 항목)) ="이어야합니다
atikot

4
@atikot, 그 라인을 만들어 내게 웃음
parek
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.