ProgramingTip

이벤트 처리기를 등록 취소하는 방법

bestdevel 2020. 11. 21. 09:30
반응형

이벤트 처리기를 등록 취소하는 방법


코드 검토에서 이벤트 처리기를 등록 취소하기 위해이 (단순화 된) 코드 조각을 우연히 발견했습니다.

 Fire -= new MyDelegate(OnFire);

이전에 등록 된 적이없는 새 델리게이트를 생성하기 때문에 이것이 이벤트라고 생각의 등록을 취소하지 않았습니다. 그러나 MSDN을 검색하면 관용구를 사용하는 여러 코드 샘플을 찾았습니다.

그래서 실험을 시작했습니다.

internal class Program
{
    public delegate void MyDelegate(string msg);
    public static event MyDelegate Fire;

    private static void Main(string[] args)
    {
        Fire += new MyDelegate(OnFire);
        Fire += new MyDelegate(OnFire);
        Fire("Hello 1");
        Fire -= new MyDelegate(OnFire);
        Fire("Hello 2");
        Fire -= new MyDelegate(OnFire);
        Fire("Hello 3");
    }

    private static void OnFire(string msg)
    {
        Console.WriteLine("OnFire: {0}", msg);
    }

}

놀랍게도 다음과 같은 일이 발생했습니다.

  1. Fire("Hello 1"); 예상대로 두 개의 메시지를 생성했습니다.
  2. Fire("Hello 2");하나의 메시지를 생성했습니다!
    이로 인해 new대의원 등록 취소가 작동 확신이 들었습니다 !
  3. Fire("Hello 3");던 많이 NullReferenceException.
    보여 뗄 수없는 코드 Firenull이벤트를 등록 해제 한 후입니다.

이벤트 나 델리게이트의 경우 컴파일러가이면에서 많은 코드를 생성한다는 것을 알고 있습니다. 그러나 나는 여전히 내 추론이 잘못된 이유를 이해하지 못합니다.

내가 무엇을 놓치고 있습니까?

추가 질문 : 사실 Fire이다 null등록 된 이벤트가 없을 때, 나는 이벤트가 해고 사방에 대한 확인이 결론을 내릴 필요 null하다.


이벤트 이벤트 호출 Delegate.Combine을 제거하는 동안 이벤트 호출을 추가하는 C # 컴파일러의 기본 구현 Delegate.Remove:

Fire = (MyDelegate) Delegate.Remove(Fire, new MyDelegate(Program.OnFire));

Framework의 구현은 자체를 Delegate.Remove보지 않고 MyDelegate대리자가 개체를 참조하는 방법 ( Program.OnFire)을 확인합니다. 기존의 MyDelegate기존 이벤트 처리기를 구독 취소 할 때 개체 를 만드는 것이 완벽하게 안전 합니다. 이 때문에 C # 컴파일러를 사용하면 이벤트 처리기를 추가 / 제거 할 때 축약 형 구문 (배후에서 동일한 코드를 생성 함)을 사용할 수 있습니다. 다음 new MyDelegate부분을 ​​생략 할 수 있습니다 .

Fire += OnFire;
Fire -= OnFire;

마지막 대리자가 이벤트 처리기에서 제거하면 Delegate.Removenull을 반환합니다. 알다시피 이벤트를 발생시키기 전에 null에 대해 이벤트를 확인하는 것이 중요합니다.

MyDelegate handler = Fire;
if (handler != null)
    handler("Hello 3");

다른 행사에서 이벤트 처리기를 구독 취소하여 가능한 경쟁 조건을 방어하기 위해 임시 지역 변수에 할당됩니다. ( 이벤트 핸들러를 로컬 변수에 할당하는 스레드 안전성에, 대한 자세한 내용 내 블로그 게시물참조하십시오 .)이 문제를 방지하는 또 다른 방법 은 항상 구독되는 빈 델리게이트를 만드는 것입니다 . 이것은 약간 더 많은 메모리를 사용하지만 이벤트는 null이 될 수 있습니다.

public static event MyDelegate Fire = delegate { };

델리게이트를 실행하기 전에 항상 타겟이 없는지 (값이 null인지) 확인해야합니다. 앞서 말했듯이이를 수행하는 한 가지 방법은 제거되지 않는 익명 메서드로 구독하는 것입니다.

public event MyDelegate Fire = delegate {};

그러나 이것은 NullReferenceExceptions를 피하기위한 해킹 일뿐입니다.

단순히 호출하기 전에 델리게이트가 null인지 확인하는 것은 다른 스레드가 null 검사 후 등록을 취소하고 호출 할 때 null로 만들 수 있기 때문에 스레드로부터 안전하지 않습니다. 다른 해결책은 대리자를 임시 변수에 복사하는 것입니다.

public event MyDelegate Fire;
public void FireEvent(string msg)
{
    MyDelegate temp = Fire;
    if (temp != null)
        temp(msg);
}

불행히도 JIT 컴파일러는 코드를 최적화하고 임시 변수를 제거하며 원래 대리자를 사용할 수 있습니다. (Juval Lowy-프로그래밍 .NET 구성 요소에 따라)

따라서이 문제를 방지하려면 대리자를 매개 변수로 받아들이는 메서드를 사용할 수 있습니다.

[MethodImpl(MethodImplOptions.NoInlining)]
public void FireEvent(MyDelegate fire, string msg)
{
    if (fire != null)
        fire(msg);
}

MethodImpl (NoInlining) 속성이 없으면 JIT 컴파일러가 메소드를 인라인하여 쓸모 없게 만들 수 있습니다. 델리게이트는 불변이므로이 구현은 스레드로부터 안전합니다. 이 방법을 다음과 같이 사용할 수 있습니다.

FireEvent(Fire,"Hello 3");

참고 URL : https://stackoverflow.com/questions/292820/how-to-correctly-unregister-an-event-handler

반응형