최신 정보
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 참조 예외는 다른 스레드가 이벤트에서 상장을 해제하는 경우에만 발생할 수 있으며이를 수행하는 유일한 이유는 해당 기술을 통해 명확하게 달성되지 않은 해당 이벤트를 통해 더 이상 호출을받지 못하도록하는 것입니다. . 당신은 경쟁 조건을 은폐 할 것입니다 - 그것을 밝히는 것이 낫습니다! null 예외는 구성 요소의 남용을 감지하는 데 도움이됩니다. 구성 요소를 악용으로부터 보호하려면 WPF 예제를 따라 생성자에 스레드 ID를 저장 한 다음 다른 스레드가 구성 요소와 직접 상호 작용하려고 시도하면 예외를 throw합니다. 그렇지 않으면 진정한 스레드 안전 구성 요소를 구현할 수 있습니다 (쉬운 작업이 아님).
따라서이 복사 / 확인 관용구를 작성하는 것은화물 컬트 프로그래밍이며 코드에 혼란과 잡음을 추가하는 것이라고 주장합니다. 실제로 다른 스레드로부터 보호하려면 훨씬 더 많은 작업이 필요합니다.
Eric Lippert의 블로그 게시물에 대한 응답으로 업데이트 :
따라서 이벤트 핸들러에 대해 놓친 중요한 사항이 있습니다. "이벤트 핸들러는 이벤트가 등록 취소 된 후에도 호출되어야하는 상황에서 견고해야합니다."따라서 분명히 이벤트의 가능성을 신경 써야합니다. 존재를 위임하다null
.이벤트 핸들러에 대한 요구 사항은 어디서나 문서화되어 있습니까?
"이 문제를 해결할 수있는 다른 방법이 있습니다 (예 : 제거되지 않는 빈 작업을 처리기로 초기화하는 것).하지만 null 검사를하는 것이 표준 패턴입니다."
그래서 제 질문의 나머지 부분은,명시 적으로 "표준 패턴"을 null-check하는 이유는 무엇입니까?빈 대리자를 할당하는 대신에= delegate {}
이벤트 선언에 추가 할 수 있습니다. 이로 인해 이벤트가 제기되는 모든 장소에서 냄새 나는 식의 작은 더미가 제거됩니다. 빈 대리자가 인스턴스화하는 것이 쉽지 않은지 확인하는 것은 쉽습니다. 아니면 아직도 뭔가 빠졌나요?
분명히 그것은 (Jon Skeet이 제안했듯이) 이것이 2005 년에 했어야했듯이 사라지지 않은 .NET 1.x의 조언 일 것임에 틀림 없다.
JIT는 조건 때문에 첫 부분에서 말하는 최적화를 수행 할 수 없습니다. 나는 이것이 얼마 전에 유령으로 제기되었지만 그것이 유효하지 않다는 것을 안다. (얼마 전에 Joe Duffy 나 Vance Morrison과 함께 확인해 보았는데 어느 것을 기억하지 못합니다.)
휘발성 수정자가 없으면 찍은 로컬 복사본의 유효 기간이 만료 될 수도 있지만 그게 전부입니다. 그것은NullReferenceException
.
그리고 맞습니다. 확실히 경쟁 조건이 있습니다. 그러나 항상 존재할 것입니다. 코드를 다음과 같이 변경한다고 가정 해보십시오.
TheEvent(this, EventArgs.Empty);
이제 해당 델리게이트의 호출 목록에 1000 개의 항목이 있다고 가정합니다. 다른 스레드가 목록의 끝 부분에서 처리기를 구독 취소하기 전에 목록 시작 부분의 작업이 실행될 가능성이 완전히 있습니다. 그러나 그 핸들러는 새로운 목록이 될 것이기 때문에 여전히 실행될 것입니다. (대의원은 불변이다.) 내가 알 수있는 한, 이것은 피할 수없는 일이다.
빈 대리자를 사용하면 무효 검사를 확실히 피할 수 있지만 경쟁 상태는 해결되지 않습니다. 또한 항상 변수의 최신 값을 "보"게 보장하지는 않습니다.
이 일을하는 확장 방법으로가는 사람들이 많이 있다는 것을 알았습니다 ...
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-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 이벤트 호출 스레드 안전성나는이 질문이 나오기 바로 전에 그 날을 출판했다. (!)
(내 테스트 설정에 결함이있어 소스 코드를 다운로드하여 직접 조사해보십시오. 모든 의견을 보내 주시면 감사하겠습니다.)
Delegate.Combine
/Delegate.Remove
이벤트에 0, 1 또는 2 명의 가입자가있는 경우 쌍; 동일한 대리인 인스턴스를 반복적으로 추가하고 제거하는 경우에는 사례 간 비용 차이가 특히 두드러 질 것입니다.Combine
인수 중 하나가 다음과 같은 경우 빠른 특수 사례 동작이 있습니다.null
(그냥 다른 하나를 반환), 그리고Remove
두 인수가 같을 때 매우 빠릅니다 (null 만 반환). - supercat
나는이 읽기를 정말로 즐겼다 - 그렇지 않다! 이벤트라고하는 C #기능을 사용하려면이 기능이 필요합니다.
왜 컴파일러에서 이것을 고치지 않습니까? 나는이 게시물을 읽은 MS 사람들이 있다는 것을 알고 있습니다.
1 - Null 문제) 왜 이벤트가 처음부터 null 대신에 비어있게 만드나요? 널 체크를 위해 얼마나 많은 코드 라인을 저장할 것인가?= delegate {}
선언문에? 컴파일러가 Empty case를 처리하도록하십시오. IE는 아무 것도하지 않습니다! 사건의 창시자에게 모든 것이 중요하다면, 그들은 비어있는 곳을 확인하고 그것을 돌보는 일을 할 수 있습니다! 그렇지 않으면 모든 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는 생성시 잠금 플래그를 설정하여 호출 될 때마다 잠금을 설정하고 등록을 취소합니다.
결론,
현대의 언어로 이런 문제를 해결할 수 있습니까?
제프리 리히터 (Jeffrey Richter)에 따르면C #을 통한 CLR올바른 방법은 다음과 같습니다.
// 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);
그것은 참조 사본을 강제하기 때문입니다. 자세한 내용은이 책의 이벤트 섹션을 참조하십시오.
Interlocked.CompareExchange
그것이 어떻게 든 통과했다면 실패 할 것이다.ref
, 그러나 그것은 전달되는 것과 같은 것이 아닙니다.ref
저장 위치 (예 :NewMail
)이 존재하고 처음에는보류하다null 참조. - supercat
이 디자인 패턴을 사용하여 이벤트 처리기가 구독 취소 된 후에 실행되지 않도록했습니다. 지금까지는 성능 프로파일 링을 시도하지는 않았지만 지금까지는 잘 작동합니다.
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);
}
}
요즘 안드로이드 용 Mono와 주로 작업 중이며, Activity가 백그라운드로 보내진 후에 View를 업데이트하려고 할 때 Android가 좋아 보이지 않는 것 같습니다.
이 관행은 특정 작업 순서를 시행하는 것이 아닙니다. 실제로 null 참조 예외를 피하는 방법입니다.
경쟁 참조가 아닌 null 참조 예외에 대해 신경을 쓰는 사람들 뒤에있는 추론은 심층적 인 심리 연구가 필요합니다. null 참조 문제를 수정하는 것이 훨씬 쉽다는 사실과 관련이 있다고 생각합니다. 일단 수정되면, 그들은 큰 "Mission Accomplished"배너를 코드에 걸어 놓고 그들의 비행복을 압축 해제합니다.
참고 : 경쟁 조건을 수정하는 것은 아마도 동기 플래그 트랙을 사용하여 처리기를 실행해야하는지
Unload
이벤트)를 사용하여 다른 이벤트에 대한 개체의 구독을 안전하게 취소 할 수 있습니다. 추잡한. 이벤트 구독 취소 요청으로 인해 이벤트가 "결국"구독 취소되고 이벤트 구독자는 이벤트가 유용한 지 여부를 확인해야합니다. - supercat
파티에 좀 늦었어요. :)
구독자가없는 이벤트를 나타 내기 위해 null 객체 패턴 대신 null을 사용하는 경우이 시나리오를 고려하십시오. 이벤트를 호출해야하지만 객체 (EventArgs)를 생성하는 것은 간단합니다. 일반적으로 이벤트에는 구독자가 없습니다. 인수를 작성하고 이벤트를 호출하기 전에 처리 노력을 투입하기 전에 구독자가 있는지 확인하기 위해 코드를 최적화 할 수 있다면 유용 할 것입니다.
이를 염두에두고 해결책은 "글쎄, 제로 가입자는 null로 표시됩니다."라고 말하는 것입니다. 그런 다음 값 비싼 작업을 수행하기 전에 null 확인을 수행하십시오. 이 작업을 수행하는 또 다른 방법은 위임 유형에 Count 속성을 사용 했으므로 myDelegate.Count가 0보다 많은 경우 비싼 작업 만 수행 할 수 있습니다. Count 속성을 사용하면 원래 문제를 해결하는 다소 좋은 패턴입니다. 최적화를 허용하고 NullReferenceException을 발생시키지 않고 호출 할 수있는 좋은 속성을 가지고 있습니다.
하지만 델리게이트는 참조 유형이므로 null이 될 수 있습니다. 아마도이 사실을 숨기고 이벤트에 대한 null 객체 패턴 만 지원하는 좋은 방법이 없었을 것입니다. 따라서 대안은 개발자가 null과 가입자가없는 것을 모두 확인하도록 강요되었을 수 있습니다. 그것은 현재 상황보다 더 추악 할 것이다.
참고 : 이것은 순수한 추측입니다. .NET 언어 나 CLR과 관련이 없습니다.
+=
과-=
. 클래스 내에서 허용되는 작업에는 호출 (내장 된 null 검사 포함), 테스트null
, 또는로 설정null
. 다른 어떤 경우에는 이름이 특정 접두사 또는 접미사가있는 이벤트 이름이 될 대리자를 사용해야합니다. - supercat
C #6 이상에서는 새로운 코드를 사용하여 코드를 단순화 할 수있었습니다..? operator
~ 같이TheEvent?.Invoke(this, EventArgs.Empty);
단일 스레드 응용 프로그램의 경우, 이것이 문제가 아닙니다.
그러나 이벤트를 노출하는 구성 요소를 만드는 경우 구성 요소의 사용자가 멀티 스레딩을 수행하지 않는다고 보장 할 수 없으며 최악의 상황에 대비해야합니다.
빈 대리자를 사용하면 문제를 해결할 수 있지만 이벤트 호출마다 성능이 저하되고 GC 관련 문제가 발생할 수 있습니다.
이 문제가 발생하기 위해서는 소비자가 탈퇴해야하지만, 임시 복사본을 지나친 경우에는 이미 전송중인 메시지를 고려하십시오.
임시 변수를 사용하지 않고 빈 대리자를 사용하지 않고 누군가가 구독을 취소하면 null 참조 예외가 발생합니다. 이는 치명적이므로 비용이 그만한 가치가 있다고 생각합니다.
필자는 일반적으로 재사용 가능한 구성 요소의 정적 메서드 (등)에서 이러한 잠재적 인 스레딩 문제를 방지하기 때문에이 문제를별로 고려하지 않았으며 정적 이벤트를 만들지 않습니다.
내가 잘못하고 있니?
모든 이벤트를 건설에 연결하고 혼자 두십시오. Delegate 클래스의 디자인은이 게시물의 마지막 단락에서 설명 하듯이 다른 용도를 올바르게 처리 할 수 없습니다.
우선, 시도하는 데 아무런 의미가 없습니다.요격하다이벤트공고너의 사건핸들러여부에 관한 동기화 된 결정을 이미 내려야합니다.통보에 응답하다..
통보를받을 수있는 사항은 모두 통보해야합니다. 이벤트 처리기가 알림을 제대로 처리하고있는 경우 (즉, 권한있는 애플리케이션 상태에 액세스하여 적절한 경우에만 응답하는 경우) 언제든지 알림을 보내고 올바르게 응답 할 것입니다.
핸들러가 이벤트가 발생했다는 통지를받지 않는 유일한 시간은 이벤트가 실제로 발생하지 않았다는 것입니다. 따라서 처리기에 알림을 보내지 않으려면 이벤트 생성을 중지하십시오 (즉, 컨트롤을 비활성화하거나 이벤트를 감지하여 처음부터 가져 오는 책임은 무엇이든).
솔직히, 나는 Delegate 클래스가 unalvageable하다고 생각한다. MulticastDelegate 로의 합병 / 전환은 큰 실수였습니다. 이벤트의 단일 정의에서 발생하는 이벤트를 시간의 경과에 따른 이벤트로 효과적으로 변경했기 때문에 유용합니다. 이러한 변경에는 논리적으로 다시 단일 순간으로 축소 할 수있는 동기화 메커니즘이 필요하지만 MulticastDelegate에는 이러한 메커니즘이 없습니다. 동기화는 이벤트가 발생하는 전체 시간 범위 또는 순간을 포함해야하므로 응용 프로그램이 동기화 된 결정을 이벤트 처리를 시작하면 완전히 처리 (트랜잭션)합니다. MulticastDelegate / Delegate 하이브리드 클래스 인 블랙 박스는 불가능한 수준이므로단일 가입자를 사용하거나 핸들러 체인을 사용 / 수정하는 동안 제거 할 수있는 동기화 핸들이있는 고유 한 종류의 MulticastDelegate 구현. 대안은 동기화 / 트랜잭션 무결성을 모든 핸들러에 중복 적으로 구현하는 것이므로 엄청나게 복잡하거나 불필요하게 복잡 할 수 있으므로이 방법을 권장합니다.
여기 좀보세요 :http://www.danielfortunov.com/software/%24daniel_fortunovs_adventures_in_software_development/2009/04/23/net_event_invocation_thread_safety이것은 올바른 해결책이며 다른 모든 대안 대신 항상 사용해야합니다.
"내부 호출 목록에는 아무것도하지 않는 무의미한 메서드로 초기화하여 멤버가 적어도 하나 이상 있는지 확인할 수 있습니다. 외부 당사자가 익명 메서드에 대한 참조를 가질 수 없으므로 외부 공급자가 메서드를 제거 할 수 없으므로 대리자가 null이 될 수 없습니다 " - Juval Löwy의 .NET 구성 요소 프로그래밍, 2 판
public static event EventHandler<EventArgs> PreInitializedEvent = delegate { };
public static void OnPreInitializedEvent(EventArgs e)
{
// No check required - event will never be null because
// we have subscribed an empty anonymous delegate which
// can never be unsubscribed. (But causes some overhead.)
PreInitializedEvent(null, e);
}
질문이 C #"이벤트"유형에 제한적이라고 생각하지 않습니다. 그 제한을 없애고 바퀴를 약간 다시 발명하고이 선을 따라 뭔가를할까요?
유용한 토론에 감사드립니다. 최근에이 문제를 해결하기 위해 조금 더 느린 클래스를 만들었지 만, 삭제 된 객체에 대한 호출을 피할 수 있습니다.
요점은 이벤트가 발생하더라도 호출 목록을 수정할 수 있다는 것입니다.
/// <summary>
/// Thread safe event invoker
/// </summary>
public sealed class ThreadSafeEventInvoker
{
/// <summary>
/// Dictionary of delegates
/// </summary>
readonly ConcurrentDictionary<Delegate, DelegateHolder> delegates = new ConcurrentDictionary<Delegate, DelegateHolder>();
/// <summary>
/// List of delegates to be called, we need it because it is relatevely easy to implement a loop with list
/// modification inside of it
/// </summary>
readonly LinkedList<DelegateHolder> delegatesList = new LinkedList<DelegateHolder>();
/// <summary>
/// locker for delegates list
/// </summary>
private readonly ReaderWriterLockSlim listLocker = new ReaderWriterLockSlim();
/// <summary>
/// Add delegate to list
/// </summary>
/// <param name="value"></param>
public void Add(Delegate value)
{
var holder = new DelegateHolder(value);
if (!delegates.TryAdd(value, holder)) return;
listLocker.EnterWriteLock();
delegatesList.AddLast(holder);
listLocker.ExitWriteLock();
}
/// <summary>
/// Remove delegate from list
/// </summary>
/// <param name="value"></param>
public void Remove(Delegate value)
{
DelegateHolder holder;
if (!delegates.TryRemove(value, out holder)) return;
Monitor.Enter(holder);
holder.IsDeleted = true;
Monitor.Exit(holder);
}
/// <summary>
/// Raise an event
/// </summary>
/// <param name="args"></param>
public void Raise(params object[] args)
{
DelegateHolder holder = null;
try
{
// get root element
listLocker.EnterReadLock();
var cursor = delegatesList.First;
listLocker.ExitReadLock();
while (cursor != null)
{
// get its value and a next node
listLocker.EnterReadLock();
holder = cursor.Value;
var next = cursor.Next;
listLocker.ExitReadLock();
// lock holder and invoke if it is not removed
Monitor.Enter(holder);
if (!holder.IsDeleted)
holder.Action.DynamicInvoke(args);
else if (!holder.IsDeletedFromList)
{
listLocker.EnterWriteLock();
delegatesList.Remove(cursor);
holder.IsDeletedFromList = true;
listLocker.ExitWriteLock();
}
Monitor.Exit(holder);
cursor = next;
}
}
catch
{
// clean up
if (listLocker.IsReadLockHeld)
listLocker.ExitReadLock();
if (listLocker.IsWriteLockHeld)
listLocker.ExitWriteLock();
if (holder != null && Monitor.IsEntered(holder))
Monitor.Exit(holder);
throw;
}
}
/// <summary>
/// helper class
/// </summary>
class DelegateHolder
{
/// <summary>
/// delegate to call
/// </summary>
public Delegate Action { get; private set; }
/// <summary>
/// flag shows if this delegate removed from list of calls
/// </summary>
public bool IsDeleted { get; set; }
/// <summary>
/// flag shows if this instance was removed from all lists
/// </summary>
public bool IsDeletedFromList { get; set; }
/// <summary>
/// Constuctor
/// </summary>
/// <param name="d"></param>
public DelegateHolder(Delegate d)
{
Action = d;
}
}
}
사용법은 다음과 같습니다.
private readonly ThreadSafeEventInvoker someEventWrapper = new ThreadSafeEventInvoker();
public event Action SomeEvent
{
add { someEventWrapper.Add(value); }
remove { someEventWrapper.Remove(value); }
}
public void RaiseSomeEvent()
{
someEventWrapper.Raise();
}
테스트
나는 다음과 같은 방식으로 그것을 테스트했다. 이 같은 개체를 만들고 파괴 스레드가 있습니다.
var objects = Enumerable.Range(0, 1000).Select(x => new Bar(foo)).ToList();
Thread.Sleep(10);
objects.ForEach(x => x.Dispose());
안에Bar
구독자 (수신기 개체) 생성자SomeEvent
(위와 같이 구현 됨) 및Dispose
:
public Bar(Foo foo)
{
this.foo = foo;
foo.SomeEvent += Handler;
}
public void Handler()
{
if (disposed)
Console.WriteLine("Handler is called after object was disposed!");
}
public void Dispose()
{
foo.SomeEvent -= Handler;
disposed = true;
}
또한 루프에서 이벤트를 발생시키는 두 개의 스레드가 있습니다.
이러한 모든 작업은 동시에 수행됩니다. 많은 청취자가 만들어지고 파괴되며 동시에 이벤트가 발생합니다.
경쟁 조건이 있다면 콘솔에서 메시지를 볼 수 있지만 비어 있습니다. 하지만 평상시와 같이 clr 이벤트를 사용하면 경고 메시지가 가득 차 있습니다. 그래서 C #에서 스레드 안전 이벤트를 구현하는 것이 가능하다고 결론을 내릴 수 있습니다.
어떻게 생각해?
disposed = true
전에 일어날foo.SomeEvent -= Handler
테스트 애플리케이션에서 거짓 긍정을 생성합니다. 하지만 그 외에도 변경할 수있는 몇 가지 사항이 있습니다. 너는 정말로 사용하고 싶어한다.try ... finally
잠금을 위해 이것은 스레드로부터 안전 할뿐만 아니라 중단 방지에도 도움이됩니다. 말할 것도없이 그 바보를 제거 할 수 있습니다.try catch
. 그리고 전달 된 대표자를 확인하지 못했습니다.Add
/Remove
- 그것은 수null
(곧바로 던져야합니다.Add
/Remove
). - Luaan
EventName(arguments)
null이 아닌 경우 대리자 만 호출하는 대신 이벤트의 대리자를 무조건 호출합니다 (null 인 경우 아무 작업도 수행하지 않음). - supercat