2014-07-17 4 views
3

현재 Observer 디자인 상황을 구현 중입니다.멀티 스레드 옵저버 패턴에서 사용할 컬렉션은 무엇입니까?

본인의 메인 클래스에 등록 된 옵서버 목록이 있습니다.

private static volatile List<IObserver> registeredObservers; 

각 관찰자는 네트워크 소켓, 소켓이 연결되어 있고 모두가 좋은 경우, 그것은 관찰 클래스 자체를 등록합니다. 오류가 있거나 소켓이 단순히 연결을 끊으면 관측 대상에서 자신을 등록 해제/제거합니다.

이 모든 것이 잘 작동하는 것 같습니다. 나는 행복하다.

private void SendEventToObservers(ILogItem item) 
{ 
    foreach (var observer in registeredObservers) 
    { 
     if (observer != null) 
     { 
      observer.OnMessageRecieveEvent(new ObserverEvent(item)); 
     } 
    } 
} 

이 후 다음과 같은 오류가 발생합니다 : : 그래서 스레드 이제

Collection was modified; enumeration operation may not execute 

나는 다른에 읽은를

하지만 지금 내 주요 클래스 I 루프에서 다음과 같이 등록 된 모든 관찰자를 통해

각각에 대해 .ToList()를 추가하려면 다음과 같이 변경해야합니다.

foreach (var observer in registeredObservers.ToList()) 

이제 q uto는 내 문제를 해결합니다. 왜냐하면 Observer가 루프를 반복하는 동안 목록에서 자체를 제거하면 .toList()가 목록의 "이전"표현을 생성하기 때문입니다.

액세스 할 때와 관찰자가 추가 및 제거 될 때 차단할 수있는 더 나은 동시 정렬 목록이 없을 것이라고 생각 했습니까?

현재의 .ToList() 솔루션이 작동합니까?

+0

FWIW의 사본 반복, 나는 volatile' 당신이 여기에 무엇을 기대하고있다'생각하지 않습니다. 'List '객체에 대한 * reference *는 목록 자체의 내용이 아니라 여기에서 휘발성이 있습니다. 목록 자체를 변경할 계획이 아니라면 권하고 싶지 않습니다. 당신은 아마'readonly'을 만드는 것이 더 좋을 것입니다. ... –

+1

또한 읽을만한 가치가 있습니다 : http://msdn.microsoft.com/en-us/library/dd997305%28v=vs.110%29.aspx –

+1

toList()에 대한 주석은 제 의견으로는 상당히 좋았습니다. 확실히 그것은 목록의 "오래된"표현을 생성하지만 반복하는 동안 목록에서 요소를 삭제하므로이 컬렉션의 다른 인스턴스를 사용해야합니다. 따라서 iterating이 registerObservers의 요소를 변경하는 동안 실제 루프는 변경할 수없는이 목록의 복사본을 사용합니다. – HimBromBeere

답변

1

열거자를 사용하는 foreach 루프를 사용할 때 열거되는 컬렉션의 내용을 수정할 수 없습니다.

그래서 소켓 컬렉션을 열거하는 동안 소켓 연결이 끊어지고 목록에서 제거되거나 새 소켓이 열리고 목록에 추가됩니다. 목록이 멀티 스레드 시나리오에서 사용되고 있기 때문에 목록에서 열거하는 동안 예외가 발생하고 예외가 발생합니다. MSDN 설명서는 표준 .NET 컬렉션이 스레드로부터 안전하지 않다는 것을 분명히합니다.

필요에 따라 System.Collections.Concurrent 네임 스페이스에있는 스레드 안전 컬렉션 (예 : System.Collections.Concurrent.ConcurrentBag<T> 또는 System.Collections.Concurrent.BlockingCollection<T>)을 사용해야합니다.

3

당신이 언급 했으니 block when being accessed and when observers are being added and removed까지는 괜찮습니다. 나는 스레드를 동기화하는 것이 가장 안전한 방법이라고 생각합니다.

시간 집약적 인 작업을 실행하지 않는 경우이 방법이 가장 좋습니다.

추가 :

lock (registeredObservers) 
    registeredObservers.Add(newObserver); 

제거 :

lock (registeredObservers) 
    registeredObservers.Remove(outOfServiceObserver); 

으로 반복 :

lock (registeredObservers) 
    foreach (var observer in registeredObservers) 
    { 
     if (observer != null) 
     { 
      observer.OnMessageRecieveEvent(new ObserverEvent(item)); 
     } 
    } 

가 UPDATE :

@fourpastmidnight 가져온 그냥 lock 목록과 작업을 할 이 솔루션 심각한 문제까지 내가 좀 더 안전하게 무엇인가에 대한 답을 수정하는 것이 좋습니다 생각 : 목록

lock (registeredObservers) 
    foreach (var observer in registeredObservers.ToArray()) 
    { 
     if (observer != null) 
     { 
      observer.OnMessageRecieveEvent(new ObserverEvent(item)); 
     } 
    } 
+0

완료되면 어떻게 잠금 해제합니까? – Zapnologica

+1

@ Zapnologica 블록 스코프의 끝에 도달하면 '잠금'이 자동으로 해제됩니다. 'lock {...} '은'System.Threading.Monitor.Enter'와'System.Threading.Monitor.Exit' 북 엔드 호출로'try ... catch ... finally' 블록을 갖는 것과 비슷합니다. – fourpastmidnight

+0

@Alireza 그러나 나는 아직도 이것이 효과가 있다고 생각하지 않는다. 목록을 반복하면서 목록이 변경되면 '열거 자'가 유효하지 않게됩니다. – fourpastmidnight