2012-06-22 14 views
8

powszechną praktyką w celu uniknięcia warunków wyścigu (w wielowątkowych aplikacjach) podczas wyzwalania zdarzeń jest następująca:Thread Safe Event zwraca

EventHandler<EventArgs> temp = SomeEvent; 
if (temp != null) temp(e); 

"Remember that delegates are immutable and this is why this technique works in theory. However, what a lot of developers don't realize is that this code could be optimized by the compiler to remove the local temp variable entirely. If this happens, this version of the code is identical to the first version, so a NullReferenceException is still possible." 

Problem (według książki) jest to, że „ten kod może być zoptymalizowane Jeśli tak się stanie, ta wersja kodu jest identyczna z pierwszą wersją, więc wyjątek NullReferenceException jest nadal możliwy "

Według CLR za pośrednictwem C#, oto lepszy sposób na zmusić kompilator do skopiowania wskaźnika zdarzenia.

virtual void OnNewMail(NewMailEventArgs e) 
{ 
    EventHandler<NewMailEventArgs> temp = 
          Interlocked.CompareExchange(ref NewMail, null, null); 
    if (temp != null) 
     temp(this, e); 
} 

Tutaj CompareExchange zmienia odniesienie NewMail NULL jeśli jest nieważna i nie zmienia NewMail jeśli nie jest null. Innymi słowy, CompareExchange w ogóle nie zmienia wartości w NewMail, ale zwraca wartość wewnątrz NewMail w sposób atomowy i wątkowy. Richter, Jeffrey (2010-02-12). CLR za pośrednictwem C# (s. 265). OReilly Media - A. Kindle Edition.

Jestem na platformie .Net 4.0, i nie jestem pewien, jak to możliwe, ponieważ Interlocked.CompareExchange oczekuje odniesienia do lokalizacji, a nie odniesienia do zdarzenia.

Albo jest błąd w książce, albo źle to zinterpretowałem. Czy ktoś zaimplementował tę metodę? A może masz lepszy sposób na zapobieganie wyścigowi?

UPDATE

to był mój błąd, kod iterlocked działa. Właśnie podałem niewłaściwy casting, ale według Bradley'a (poniżej) nie jest to konieczne w .net 2.0 i wyżej w oknach.

+0

http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx może być interesujące – Vlad

Odpowiedz

7

Kompilator (lub JIT) nie może zoptymalizować tego if/temp (w CLR 2.0 i późniejszych); CLR 2.0 Memory Model nie zezwala na wprowadzanie odczytów ze sterty (reguła # 2).

Tak więc MyEvent nie można odczytać po raz drugi; wartość temp należy przeczytać w instrukcji if.

Zapoznaj się z my blog post, aby uzyskać szerszą dyskusję na temat tej sytuacji i wyjaśnienie, dlaczego standardowy wzór jest w porządku.

Jednakże, jeśli używasz CLR innego niż Microsoft (np. Mono), który nie zapewnia modelu pamięci CLR 2.0 gwarantuje (ale tylko podąża za modelem pamięci ECMA) lub używasz Itanium (który ma słaby model pamięci sprzętowej), będziesz potrzebować kodu takiego jak Richter, aby wyeliminować potencjalne warunki wyścigu.

W odniesieniu do Twojego pytania o Interlocked.CompareExchange składnia public event EventHandler<NewMailEventArgs> NewMail jest cukier syntaktyczny tylko C# do deklarowania prywatną pole typu EventHandler<NewMailEventArgs> i imprezy publicznej, która ma add i remove metod. Wywołanie Interlocked.CompareExchange odczytuje wartość prywatnego pola EventHandler<NewMailEventArgs>, więc ten kod jest kompilowany i działa tak, jak opisuje Richter; jest to po prostu niepotrzebne w CLR Microsoftu.

+0

zaktualizowałem swój przykład komentarzami autora. jeśli to, co mówisz, jest prawdą, to jest bezpośrednio sprzeczne z tym, co mówi Jeffrey Richter.I złapałem go z niedokładnymi oświadczeniami w tej książce przed .. Chciałbym tylko potwierdzić poprawny sposób robienia tego .. –

+0

Tylko w windows/.Net complier/JIT, to nieprawda dla Mono. http://www.ecma-international.org/publications/standards/Ecma-335.htm – caesay

+0

@caesay: Dobra uwaga; Zrobiłem to w moim blogu, ale nie w odpowiedzi; Zaktualizuję. –

1

Przypuszczam, że tęsknisz za interpretacją. Lokalizacja oznacza tylko wskaźnik do obiektu referencyjnego [msdn version: Obiekt docelowy, który jest porównywany z porównaniem i ewentualnie zastępowany.]. Poniższy kod działa dobrze w .NEt 4.0

public class publisher 
{ 

    public event EventHandler<EventArgs> TestEvent; 
    protected virtual void OnTestEvent(EventArgs e) 
    { 
     EventHandler<EventArgs> temp = Interlocked.CompareExchange(ref TestEvent, null, null); 
     if (temp != null) 
      temp(this,e); 
    } 
} 
3

Teraz jest to tylko częściowa odpowiedź na swoje pytanie, bo nie mogę wypowiedzieć się na temat korzystania Interlocked.CompareExchange, jednak myślę, że ta informacja może być przydatna.

Problemem jest to, że kompilator może zoptymalizować że jeśli/temp się,

A według CLR poprzez C# (str. 264-265)

[W ] kod mógł zostać zoptymalizowany przez kompilator, aby całkowicie usunąć lokalną [...] zmienną. Jeśli tak się stanie, ta wersja kodu jest identyczna z [wersją, która odwołuje się do zdarzenia dwukrotnie], więc wyjątek NullReferenceException jest nadal możliwy.

Tak, to jest możliwe , jednak ważne jest, aby wiedzieć, że just-in-time (JIT) kompilator Microsoftu nie kiedykolwiek optymalizacji dala zmiennej lokalnej. Choć może to ulec zmianie, jest mało prawdopodobne, ponieważ prawdopodobnie zepsułoby to wiele aplikacji.

To dlatego Net ma silny model memory: http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S5

odczytuje i zapisuje nie może być wprowadzony.

i

model nie pozwala czyta zostać wprowadzony, jednak, ponieważ oznaczałoby to refetching wartość z pamięci, aw niskich blokady pamięci kodu może być zmienia.

 

jednak Mono, co następuje much weaker memory model, można zoptymalizować tej zmiennej lokalnej z dala. Podsumowanie: Jeśli nie planujesz używać Mono, nie przejmuj się tym.

I nawet wtedy to zachowanie może zostać stłumione przez niestabilne deklaracje.

+0

masz rację. Podskoczyłem do wniosku. dostosowałem mój post i usunąłem to "zoptymalizowane" oświadczenie. –

0

I spojrzeć na IL produkowane zobaczysz, że metoda nazywa się ten

IL_000d: ldsflda class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs> ConsoleApplication1.Program::MyEvent 
IL_0012: ldnull 
IL_0013: ldnull 
IL_0014: call  !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs>>(!!0&,!!0,!!0) 

zobaczyć, że ldsflda - mój wydarzenie jest statyczna, ale to załadowanie adresu dziedzinie. To pole jest automatycznie wygenerowanym polem uprawnień, które kompilator generuje dla każdego zdarzenia.

pole jest zdefiniowane tak:

.field private static class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs> MyEvent