최신 정보
C # 6 부터이 질문에 대한 답변은 다음과 같습니다.
SomeEvent?.Invoke(this, e);
다음과 같은 조언을 자주 듣거나 읽습니다.
이벤트를 확인하고 시작하기 전에 항상 이벤트 사본을 만드 null
십시오. 이렇게하면 null
null을 확인하는 위치 와 이벤트 를 발생시키는 위치 사이에 이벤트가 발생하는 스레딩 관련 잠재적 문제가 제거됩니다 .
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
업데이트 : 이벤트 멤버가 일시적이어야 할 수 있다는 최적화에 대해 읽었지만 Jon Skeet은 CLR이 복사본을 최적화하지 않는다고 대답했습니다.
그러나이 문제가 발생하기 위해서는 다른 스레드가 다음과 같은 작업을 수행해야합니다.
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
실제 순서는 다음과 같습니다.
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
OnTheEvent
저자가 구독을 취소 한 후에 실행 되는 요점은 이러한 일이 발생하지 않도록 특별히 구독을 취소 한 것입니다. 실제로 필요한 것은 접근 자 add
와 remove
접근 자 에서 적절한 동기화를 통해 사용자 정의 이벤트 구현입니다 . 또한 이벤트가 발생하는 동안 잠금이 유지되면 교착 상태가 발생할 수 있습니다.
그래서이있다 화물 숭배 프로그래밍 ? 그런 식으로 보입니다-많은 사람들이 여러 스레드로부터 코드를 보호하기 위해이 단계를 수행해야합니다. 실제로 이벤트가 멀티 스레드 디자인의 일부로 사용되기 전에 이것보다 훨씬 더주의를 기울여야 할 것 같습니다 . 따라서 추가 관리를하지 않는 사람들은이 조언을 무시할 수도 있습니다. 단일 스레드 프로그램에서는 문제가되지 않으며 실제로 volatile
대부분의 온라인 예제 코드가없는 경우에는 조언이 없을 수도 있습니다. 전혀 효과.
(그리고 delegate { }
멤버 선언에 빈 것을 할당하는 것이 훨씬 간단하지 않으므로 null
처음 부터 확인할 필요가 없습니다 .)
업데이트 :명확하지 않은 경우 모든 상황에서 null 참조 예외를 피하기 위해 조언의 의도를 파악했습니다. 내 요점은이 특정 null 참조 예외가 다른 스레드가 이벤트에서 delisting하는 경우에만 발생할 수 있으며 그 이유는 해당 기술을 통해 더 이상 호출이 수신되지 않도록하는 것입니다. . 경쟁 조건을 숨기고있을 것입니다. 공개하는 것이 좋습니다. 이 null 예외는 구성 요소의 남용을 감지하는 데 도움이됩니다. 구성 요소가 악용되지 않도록하려면 WPF의 예를 따르십시오. 스레드 ID를 생성자에 저장 한 다음 다른 스레드가 구성 요소와 직접 상호 작용하려고하면 예외를 throw 할 수 있습니다. 또는 진정한 스레드 안전 구성 요소를 구현하십시오 (쉬운 작업은 아님).
그래서 나는 단지이 카피 / 체크 관용구를하는 것은화물 컬트 프로그래밍이며, 코드에 혼란과 소음을 추가한다고 주장한다. 실제로 다른 스레드로부터 보호하려면 더 많은 작업이 필요합니다.
Eric Lippert의 블로그 게시물에 대한 답변으로 업데이트 :
따라서 이벤트 처리기에서 놓친 주요한 사항이 있습니다. “이벤트 처리기가 구독 취소 된 후에도 호출 될 때 이벤트 처리기가 강력해야합니다.” 대리자 null
. 이벤트 핸들러에 대한 요구 사항이 어디에나 문서화되어 있습니까?
“이 문제를 해결하는 다른 방법이 있습니다. 예를 들어, 처리기가 초기화되지 않은 빈 동작을 갖도록 초기화하는 것입니다. 그러나 null 검사를 수행하는 것이 표준 패턴입니다.”
그래서 내 질문의 나머지 조각은 왜 명시 적 null 검사가 “표준 패턴”입니까? 빈 델리게이트를 할당하는 대안 = delegate {}
은 이벤트 선언에 추가하기 만하면되며, 이렇게하면 이벤트가 발생하는 모든 장소에서 악의적 인 행사 더미가 제거됩니다. 빈 델리게이트가 인스턴스화하기에 저렴하다는 것을 쉽게 알 수 있습니다. 아니면 여전히 뭔가 빠졌습니까?
Jon Skeet이 제안한대로 2005 년에했던 것처럼 죽지 않은 .NET 1.x 조언 일 것입니다.
답변
JIT는 조건 때문에 첫 번째 부분에서 이야기하는 최적화를 수행 할 수 없습니다. 나는 이것이 오래 전에 유령으로 제기되었다는 것을 알고 있지만 유효하지 않습니다. (나는 얼마 전에 Joe Duffy 또는 Vance Morrison으로 확인했지만 어느 것을 기억할 수 없습니다.)
휘발성 수정자가 없으면 로컬 복사본이 오래되었을 수 있지만 그게 전부입니다. 을 발생시키지 않습니다 NullReferenceException
.
물론 그렇습니다. 경쟁 조건이 있지만 항상있을 것입니다. 코드를 다음과 같이 변경한다고 가정합니다.
TheEvent(this, EventArgs.Empty);
이제 해당 대리자의 호출 목록에 1000 개의 항목이 있다고 가정하십시오. 다른 스레드가 목록의 끝 근처에서 핸들러를 구독 취소하기 전에 목록 시작시 조치가 실행되었을 가능성이 있습니다. 그러나 해당 핸들러는 새 목록이므로 계속 실행됩니다. (대표는 불변이다.) 내가 볼 수있는 한, 이것은 불가피하다.
빈 델리게이트를 사용하면 nullity 검사를 피할 수 있지만 경쟁 조건을 수정하지는 않습니다. 또한 항상 변수의 최신 값을 “볼”것을 보장하지는 않습니다.
답변
나는 이것을하는 확장 방법을 향해 많은 사람들을 봅니다 …
public static class Extensions
{
public static void Raise<T>(this EventHandler<T> handler,
object sender, T args) where T : EventArgs
{
if (handler != null) handler(sender, args);
}
}
이벤트를 발생시키는 더 좋은 구문을 제공합니다 …
MyEvent.Raise( this, new MyEventArgs() );
또한 메소드 호출시 캡처되므로 로컬 사본을 제거합니다.
답변
“왜 ‘표준 패턴’을 명시 적으로 null 검사합니까?”
그 이유는 null-check가 더 성능이 좋기 때문일 수 있습니다.
이벤트가 생성 될 때 항상 빈 델리게이트를 구독하면 몇 가지 오버 헤드가 발생합니다.
- 빈 델리게이트 구성 비용.
- 이를 포함 할 델리게이트 체인 구성 비용.
- 이벤트가 발생할 때마다 무의미한 대리인을 호출하는 비용.
(UI 컨트롤에는 이벤트가 많지만 대부분 구독하지 않습니다. 각 이벤트에 더미 구독자를 만든 다음 호출하면 성능이 크게 저하 될 수 있습니다.)
subscribe-empty-delegate 접근법의 영향을 확인하기 위해 약간의 성능 테스트를 수행했으며 결과는 다음과 같습니다.
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took: 432ms
OnClassicNullCheckedEvent took: 490ms
OnPreInitializedEvent took: 614ms <--
Subscribing an empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took: 674ms
OnClassicNullCheckedEvent took: 674ms
OnPreInitializedEvent took: 2041ms <--
Subscribing another empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took: 2011ms
OnClassicNullCheckedEvent took: 2061ms
OnPreInitializedEvent took: 2246ms <--
Done
가입자가 0 명 또는 1 명인 경우 (이벤트가 많은 UI 컨트롤에 공통), 빈 델리게이트로 사전 초기화 된 이벤트는 현저하게 느립니다 (5 천만 회 이상 반복).
자세한 내용과 소스 코드를 보려면 이 질문을하기 바로 전에 게시 한 .NET 이벤트 호출 스레드 안전성 에 대한이 블로그 게시물을 방문하십시오 (!).
(테스트 설정에 결함이있을 수 있으므로 소스 코드를 다운로드하여 직접 검사하십시오. 의견은 대단히 감사합니다.)
답변
나는이 읽기를 정말로 즐겼습니다 – 아닙니다! events라는 C # 기능을 사용하려면 필요하지만!
컴파일러에서 이것을 고치지 않겠습니까? 이 게시물을 읽는 MS 사람들이 있다는 것을 알고 있으므로 이것을 화 내지 마십시오!
1-Null 문제 ) 왜 이벤트를 null 대신에 비워 두지 않겠습니까? null 검사를 위해 몇 줄의 코드를 저장하거나 = delegate {}
선언 을 고수 해야합니까? 컴파일러가 Empty 케이스를 처리하도록하자. IE는 아무것도하지 않는다! 모든 것이 이벤트 제작자에게 중요한 경우, .Empty를 확인하고 관심있는 모든 작업을 수행 할 수 있습니다! 그렇지 않으면 모든 null 확인 / 대리자 추가가 문제를 해결하는 것입니다!
솔직히 나는 모든 이벤트-일명 상용구 코드 로이 작업을 수행하는 데 피곤합니다!
public event Action<thisClass, string> Some;
protected virtual void DoSomeEvent(string someValue)
{
var e = Some; // avoid race condition here!
if(null != e) // avoid null condition here!
e(this, someValue);
}
2-경쟁 조건 문제 ) Eric의 블로그 게시물을 읽었으며 H (핸들러)가 자체를 역 참조 할 때 처리해야하지만 이벤트를 변경할 수없는 스레드로 만들 수 없다는 데 동의합니다. IE, 생성시 잠금 플래그를 설정하여 호출 될 때마다 모든 구독 및 구독을 잠그는 동안 실행합니까?
결론 ,
현대 언어는 우리에게 이런 문제를 해결하지 않습니까?
답변
함께 C # 6 위, 새로운 코드는 사용을 간략화 할 수 ?.
있는 바와 같이 운영자 :
TheEvent?.Invoke(this, EventArgs.Empty);
MSDN 설명서는 다음과 같습니다 .
답변
C #을 통한 CLR 책의 Jeffrey Richter에 따르면 올바른 방법은 다음과 같습니다.
// Copy a reference to the delegate field now into a temporary field for thread safety
EventHandler<EventArgs> temp =
Interlocked.CompareExchange(ref NewMail, null, null);
// If any methods registered interest with our event, notify them
if (temp != null) temp(this, e);
참조 사본을 강제 실행하기 때문입니다. 자세한 내용은이 책의 이벤트 섹션을 참조하십시오.
답변
이 디자인 패턴을 사용하여 이벤트 핸들러가 등록 해제 된 후에 실행되지 않도록했습니다. 성능 프로파일 링을 시도하지 않았지만 지금까지는 잘 작동하고 있습니다.
private readonly object eventMutex = new object();
private event EventHandler _onEvent = null;
public event EventHandler OnEvent
{
add
{
lock(eventMutex)
{
_onEvent += value;
}
}
remove
{
lock(eventMutex)
{
_onEvent -= value;
}
}
}
private void HandleEvent(EventArgs args)
{
lock(eventMutex)
{
if (_onEvent != null)
_onEvent(args);
}
}
요즘에는 주로 Android 용 Mono로 작업하고 있으며 활동이 백그라운드로 전송 된 후 View를 업데이트하려고하면 Android가 좋아하지 않는 것 같습니다.