2008-11-12 3 views
31

나에게 혼란 스럽지만 아무런 문제도 발생하지 않은 이벤트를 발송하는 권장 방법은 다음과 같습니다 :이벤트 발송 전에 널 확인 중 ... 스레드 안전?

public event EventHandler SomeEvent; 
... 
{ 
    .... 
    if(SomeEvent!=null)SomeEvent(); 
} 

다중 스레드 환경에서이 코드는 다른 스레드가 null 검사와 이벤트 호출 사이에서 SomeEvent의 호출 목록을 변경 하시겠습니까?

답변

53

당신이 지적으로, 어디에 여러 스레드가 동시에 SomeEvent에 액세스 할 수 있습니다, 하나 개의 스레드는 SomeEvent가 null 여부를 확인하고 있음을 확인할 수 있었다 그렇지 않습니다. 이렇게하면 다른 스레드가 마지막으로 등록한 대리자를 SomeEvent에서 제거 할 수 있습니다. 첫 번째 스레드가 SomeEvent을 발생 시키려고하면 예외가 발생합니다. 이 시나리오를 피하기 위해 합리적인 방법은 다음과 같습니다

이 대리자의하는 Delegate.Combine 및 Delegate.Remove 정적 접근을 추가의 기본 구현을 사용하여 제거 이벤트에서 추가 또는 제거 할 때마다 때문에 작동
protected virtual void OnSomeEvent(EventArgs args) 
{ 
    EventHandler ev = SomeEvent; 
    if (ev != null) ev(this, args); 
} 

방법이 사용됩니다. 이러한 각 메서드는 전달 된 대리자를 수정하는 대신 대리인의 새 인스턴스를 반환합니다.

또한 .NET에서 개체 참조를 할당하는 작업은 atomic이고 추가 및 제거 이벤트 접근 자의 기본 구현은 synchronised입니다. 위의 코드는 먼저 멀티 캐스트 대리자를 이벤트에서 임시 변수로 복사하여 성공합니다. 이 시점 이후에 SomeEvent를 변경해도 사용자가 작성하여 저장 한 사본에는 영향을 미치지 않습니다. 따라서 이제는 모든 대리자가 등록되었고 이후에 호출 될 수 있는지 여부를 안전하게 테스트 할 수 있습니다. 이 솔루션이 호출 할 때, 즉 그 이벤트 핸들러가 null 인, 하나의 인종 문제를 해결하는 것이

참고. 이벤트 핸들러가 호출 될 때 이벤트 처리기가 작동하지 않거나 복사가 수행 된 후에 이벤트 핸들러가 등록하는 문제는 처리하지 않습니다. 이벤트 핸들러는 즉시 핸들러가 해제 가입되어 파괴있어 상태에 의존하는 경우

예를 들어,이 솔루션은 제대로 실행되지 수있는 코드를 호출 할 수 있습니다. 자세한 내용은 Eric Lippert's excellent blog entry을 참조하십시오. 또한 this StackOverflow question and answers을 참조하십시오.

편집 : 당신은 C# 6.0을 사용하는 경우, 다음 Krzysztof's answer 갈 수있는 좋은 방법처럼 보인다.

+0

".NET에서 개체 참조의 할당은 스레드로부터 안전합니다."라는 문구에 문제가 있습니다. 당신은 반드시 원자를 의미합니까? 내가 아는 한, 스레드 A가 변수 V에 대한 참조를 설정하면, V가 휘발성이 아니거나 읽기 및 쓰기 V를 사용하는 동안 스레드 B가 업데이트 된 참조를 변수 V에 설정한다는 것을 보증하지 않습니다. –

+0

또한 당신의 본보기가 깨졌습니다. 스레드 A가 SomeEvent에 이벤트 핸들러를 추가 한 다음 스레드 B가 SomeEvent를 호출하면 SomeEvent가 volatile로 선언되지 않는 한 스레드 B가 SomeEvent를 null로 간주하는 경우가 있습니다. –

+1

@Christophe, "thread-safe"로 말합니다. 하나의 쓰레드에서 객체 참조를 할당하는 것이 다른 쓰레드에 의해 일관성없는 것으로서 중단되거나 보이지 않는다. 내 업데이트에서 언급했듯이 스레드 A와 스레드 B가 항상 동일한 이벤트 뷰를 갖는다는 것은 분명히 똑같지는 않습니다. 이 모든 예는 모든 경쟁 조건이 아닌 특정 경쟁 조건을 방지하는 것입니다. – RoadWarrior

4

권장되는 방법은 임시 약간 다릅니다 및 사용

EventHandler tmpEvent = SomeEvent; 
if (tmpEvent != null) 
{ 
    tmpEvent(); 
} 
+0

그래서 호출 목록을 복제합니까? – spender

+0

예 - 예를 들어 편집하겠습니다. –

+0

또는 GetInvocationList를 임시로 호출하고 각각을 순서대로 실행할 수 있습니다. –

3

안전한 방법 :

 

public class Test 
{ 
    private EventHandler myEvent; 
    private object eventLock = new object(); 

    private void OnMyEvent() 
    { 
     EventHandler handler; 

     lock(this.eventLock) 
     { 
      handler = this.myEvent; 
     } 
     if (handler != null) 
     { 
      handler(this, EventArgs.Empty); 
     } 
    } 

    public event MyEvent 
    { 
     add 
     { 
      lock(this.eventLock) 
      { 
       this.myEvent += value; 
      } 
     } 
     remove 
     { 
      lock(this.eventLock) 
      { 
       this.myEvent -= value; 
      } 
     } 

    } 
} 
 

이 - 빌

+1

이것은 Jon Skeet이 http://www.yoda.arachsys.com/csharp/events.html에서 제공 한 올바른 접근 방법입니다. –

+1

핸들러 할당을 동기화와 동기화했지만 퇴장 후 null을 확인하지 않습니다. 동기화 된 블록은 여전히 ​​동일한 문제를 허용합니까? – user12345613

21

가장 간단한 방법이 널 체크를 제거 익명 위임하는 이벤트 핸들러를 할당하는 것입니다. 벌금은 거의 발생하지 않으며 널 체크, 경기 조건 등을 모두 면제 해줍니다.

public event EventHandler SomeEvent = delegate {};

관련 질문 : Is there a downside to adding an anonymous empty delegate on event declaration?

+0

이것이 왜 "공식적인"답변으로 표시되지 않습니까? 이것은 * 유일한 * 좋은 대답입니다. – yfeldblum

+4

동의하지 않습니다. 이 방법은 해킹처럼 보이고 발포 이벤트를 "안전"하거나 신뢰할 수 없게 만들지 않으며 null 확인 문제 만 해결합니다. 관련 스레드에서 내 설명을 참조하십시오. –

0

나는 이벤트 핸들러에 대한 확장자 기능을 활용 에서의 RoadWarrior의 대답에 약간의 improvment을 제안하고 싶습니다 :이 확장으로

public static class Extensions 
{ 
    public static void Raise(this EventHandler e, object sender, EventArgs args = null) 
    { 
     var e1 = e; 

     if (e1 != null) 
     { 
      if (args == null) 
       args = new EventArgs(); 

      e1(sender, args); 
     }     
    } 
    } 

을 범위에서 이벤트는 다음에 의해 간단히 제기 될 수 있습니다.

클래스 SomeClass { 공개 이벤트 EventHandler MyEvent; C#에서

void SomeFunction() 
{ 
    // code ... 

    //--------------------------- 
    MyEvent.Raise(this); 
    //--------------------------- 
} 

}

14

6.0 당신은 널 (null)를 확인하고 쉽고 스레드로부터 안전한 방식으로 이벤트를 발생하는 모나드 널 조건 연산자 ?.를 사용할 수 있습니다.

SomeEvent?.Invoke(this, args); 

왼쪽 측면을 한 번만 평가하고 임시 변수에 유지하므로 스레드로부터 안전합니다. 부분적으로 Null-conditional operators라는 제목의 here을 더 읽을 수 있습니다.

+0

vs2015로 업그레이드하고 새로운 언어 기능을 사용할 수있는 기회를 얻었을 때 올바른 답으로 표시 될 것입니다. – spender

+0

니스. 이것은 이제'THE'' 사실상의 접근법으로 보입니다. – StuartLC