2012-11-21 3 views
1

여러 스레드에서 해시를 추가하려고합니다. 항목을 이미 업데이트하고 싶으면 항목을 추가하고 싶으면 목록에 추가하고 싶습니다.여러 스레드에서 HashSet을 추가 할 때 오류가 발생했습니다.

코드를 사용하면 여러 개의 항목이 갑자기 같은 참조를 가리키고 있기 때문에 많은 중복으로 끝납니다. 나는 이것이 어디서 또는 왜 일어나는지 볼 수 없다.

다음은 내가 처음으로 문제를 볼 때 끝나는 "로그"문자열 다음에 사용하는 코드입니다. 갑자기 이미 추가 된 모든 항목의 값이 같음을 알 수 있습니다.

lock (_remoteDevicesLock) 
{ 
    RemoteDevice rDevice = new RemoteDevice(notifyMessage.UUID, notifyMessage.Location); 
    log += notifyMessage.UUID + " " + rDevice.UUID; 
    if (!_remoteDevices.Add(rDevice)) 
    { 
     log += " Not Added \r\n"; 
     rDevice = (from d in _remoteDevices 
        where d.UUID.Trim().Equals(notifyMessage.UUID.Trim(), StringComparison.OrdinalIgnoreCase) 
        select d).FirstOrDefault(); 
     if (rDevice != null) 
     { 
      //Update Device Expire Time 
     } 
    }        
    else 
    { 
     log += " Added \r\n Current HashSet: \r\n"; 

     foreach (RemoteDevice rd in _remoteDevices) 
     { 
      log += rd.UUID + " \r\n"; 
     } 
    } 
} 


00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-1000-001cdf885737 

00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-1000-001cdf885737 
00000000-0000-0001-0002-001cdf885737 

00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-1000-001cdf885737 
00000000-0000-0001-0002-001cdf885737 
00000000-0000-0001-0001-001cdf885737 

00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-1000-001cdf885737 
00000000-0000-0001-0002-001cdf885737 
00000000-0000-0001-0001-001cdf885737 
00000000-0000-0001-0000-001cdf885737 

00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-0000-001cdf885737 
00000000-0000-0001-0000-001cdf885737 
00000000-0000-0001-0000-001cdf885737 
00000000-0000-0001-0000-001cdf885737 
00000000-0000-0001-1000-001cdf885737 

업데이트 : 여기 GetHashCode입니다 그리고 내가 수동 검사와 목록을 사용하여도 문제가 있었다되었을 때 문제가 여기에있다 생각하지 않지만 요청에 따라 같음.

public override bool Equals(object obj) 
{ 
    var other = obj as RemoteDevice; 
    if (other == null) 
    { 
     return false; 
    } 
    else 
    { 
     return UUID.Trim().Equals(other.UUID.Trim(), StringComparison.OrdinalIgnoreCase); 
    } 
} 

public override int GetHashCode() 
{ 
    return UUID.GetHashCode(); 
} 
+0

1. 클래스 외부에 노출 된 객체를 잠그지 말고 잠그기 위해 특정 객체를 만들어야합니다. 2. 문자열 연결을 사용하여 로그 문자열에 추가하지 마십시오. ,'StringBuilder'를 사용하십시오. – Servy

+0

RemoteDevice.Equals와 RemoteDevice.GetHashCode는 어떻게 보입니까? – fsimonazzi

+0

이 스레드를 검사 할 수 있습니다. http://stackoverflow.com/questions/4306936/how-to-implement-concurrenthashset-in-net –

답변

1

의도적으로 세트에있는 항목의 해시 코드는 세트 내부에있는 동안 절대 발생하지 않습니다. 이 집합은 객체의 내부 상태가 변경된 것을 감지 할 방법이 없으므로 이전 해시 값의 '버킷'에 있으므로 새 해시 코드가있는 다른 항목을 추가하려고하면 " 버킷 "이 비어 있고 항목을 추가하십시오. 현재 세트에있는 동안 항목을 "변경"하려면이를 제거하고 변경 한 다음 다시 추가하십시오. 또는 (디자인 관점에서) 더 나은 방법은 이전 값을 제거하고 새 객체를 추가하는 것입니다 전적으로 (아마도 제거 된 부분에서 복사 된 특정 부분이 있음).

문제가있는 것으로 보입니다. 나는 당신이 직면 한 문제가 아닌데도 나머지 제안 사항을 아래에 남겨 둘 것입니다.

RemoteDevice 클래스는 의미있는 구현 인 EqualsGetHashCode을 무시하지 않을 수도 있습니다. object에 정의 된 기본 구현은 객체의 메모리에있는 주소를 기반으로하므로 같은 값을 가진 두 개의 다른 인스턴스가 해당 정의에서 "같지 않음"이됩니다. 단일 GUID가 유효한 것으로 보입니다. 고유 ID는 구현이 단지에 연기해야한다 (GUID는 합리적인 EqualsGetHashCode 정의가)

예 :.. 또한 당신이 lock 작품 lock(myObject)를 사용하여 어떤 방해하지 않는 방법 오해 것으로 보인다

public class RemoteDevice 
{ 
    public Guid UUID { get; set; } 

    public override bool Equals(object obj) 
    { 
     RemoteDevice other = obj as RemoteDevice; 
     if (other == null) return false; 
     return UUID.Equals(other.UUID); 
    } 

    public override int GetHashCode() 
    { 
     return UUID.GetHashCode(); 
    } 
} 

다른 물체도 myObject을 사용합니다. 동일한 인스턴스에을 입력하면 lock을 종료 할 때까지 기다렸다가 입력 할 수 있습니다. 즉, 누구든지 HashSet에 액세스하기 전에 객체의 동일한 인스턴스에 코드가 lock이어야합니다 (HashSet이 다중 스레드에서 사용되도록 설계되지 않았기 때문에).

이렇게하는 것이 바람직하지 않거나 바람직하지 않은 경우 여러 스레드에서 액세스 할 수있는 컬렉션을 만들어야합니다. 많은 컬렉션은 System.Collections.Concurrent에 구현되어 있지만 불행히도 ConcurrentSet은 없습니다. 몇 가지 옵션이 있습니다. 우리는 하나를 위해 우리 자신을 만들 수도 있지만 또 다른 옵션은 ConcurrentDictionary을 사용하고 단순히 값을 무시하고 키를 사용하는 것입니다. 다소 혼란 스러울 지 모르겠지만 자신 만의 동시 수집을 효과적으로 만드는 것은 어렵습니다. 개인적으로, 내가 하나만 사용하고자한다면 ConcurrentDictionary 주위에 래퍼를 생성하여 쌍을 저장한다는 사실을 숨 깁니다.

+0

질문을 업데이트했고 재정의가 거의 동일하게 보입니다. – Oli

+0

저는 자물쇠 문제에 대해 아직도 혼란스러워합니다. _remoteDevicesLock이 아닌 _remoteDevicesLock을 잠급니다. 스레드가 기다려서 한 번에 하나씩 실행하여 해시셋 스레드를 안전하게 만드는 코드 섹션을 잠그고 있습니다. 제가 그 일을하지 않는다면 이유와 제가 잘못하고있는 것을 설명하십시오. – Oli

+1

Servy- "세트 안의 항목의 HashCode가 절대 위험하지 않습니다"라는 것이 문제입니다. 밝혀지면 (나는 브랜드 이름을 밝히지 않겠다) 내 무선 라우터는 UUID를 Upnp의 사양에 맞게 변경 한 것입니다. 나는이 문제를 코딩하는 방법을 알아 내야 할 것 같다. – Oli

2

다음 두 개의 UUIDs은 다른 해시 코드를 갖지만 같음 : "x", "x"와 비교합니다. 이유 : 공백을 다르게 취급하고 있습니다.

GetHashCodeEquals을 constent로 설정해야합니다. Equals이 true를 반환하면 의 두 해시 코드는이어야합니다. 이 계약을 준수하지 않을 경우 HashSet은 정의되지 않은 방식으로 작동합니다 (중복 가능).

해결책 : 두 위치 모두 Trim이거나 없음.

+0

방금 ​​문제를 해결하고 두 곳 모두에서 트리밍을 시도했습니다. – Oli

0

사전을 사용하면 코드가 훨씬 간단하고 강력 해지고 GetHashCode 재정의에 의존하지 않게됩니다. 조회도 훨씬 빠를 것입니다. LINQ 쿼리는 성능이 현저하지 않습니다. 동일한 값을 두 번 이상 계산하지 않는 것과 같은 단순한 작업에 특히주의해야합니다. 특히 루프에있는 경우에 특히 그렇습니다. 나는. notifyMessage.UUID.Trim()은 많은 장치가 목록에있는 것처럼 LINQ 쿼리에서 여러 번 호출됩니다. Id는 루프 전에 한 번 계산되어 재사용되어야합니다.

var deviceId = notifyMessage.UUID.Trim().ToLowerInvariant(); 

RemoteDevice remoteDevice; 

if (_remoteDevices.TryGetValue(deviceId, out remoteDevice)) 
{ 
    UpdateDevice(remoteDevice); 
} 
else 
{ 
    var newDevice = CreateDevice(notifyMessage); 

    _remoteDevices.Add(deviceId, newDevice); 
} 

위의 코드는 귀하의 질문에, _remoteDevices.Add 모두와의 코드에서 단일 조회 대신 두 가지를 않습니다 ...

var _remoteDevices = new Dictionary<string, RemoteDevice>(); 

:

여기 샘플 사용하여 사전입니다 LINQ 쿼리는 조회를 수행합니다. 컴파일러가 표현식을 FirstOrDefault (d => d.UUID.Trim(). Equals (notifyMessage.UUID.Trim())로 변환 할만큼 충분히 똑똑하지 않은 경우 FirstOrDefault 대신 Where를 사용하므로 LINQ 쿼리는 실제로 전체 반복을 수행합니다. , StringComparison.OrdinalIgnoreCase)

관련 문제