Zdarzenia w C#

Przypuśćmy że mamy zdarzenie

public event EventHandler<EventArgs> Bang;

Aby je wywołać, wystarczy napisać Bang(this, EventArgs.Empty);

Nie… Jeśli do zdarzenia nie jest podpięta żadna procedura obsługi tegoż, otrzymamy NullReferenceExcepion.

A zatem if (Bang != null) Bang(this, EventArgs.Empty);

Nie… Taka konstrukcja będzie się znakomicie sprawdzała tak długo, jak długo nasza aplikacja będzie jednowątkowa. Jeśli wątków będzie wiecej, może zdarzyć się sytuacja, kiedy jeden wątek będzie próbował wywołać zdarzenie a drugi opróżniał jego listę inwokacji. Szeregowaniem wątków i procesów zajmuje się system operacyjny i nie mamy wpływu na to, kiedy nastąpi przełączenie kontekstu. Jak będziemy mieli pecha, instrukcja czyszcząca listę inwokacji zdarzenia zostanie czasowo wciśnięta pomiędzy if (Bang != null) a Bang(...). I znów będzie wyjątek.

A zatem:

W większości przypadków, to już wystarczy. Ale…

Co się stanie, jeżeli jedna z procedur obsługi zdarzenia wyrzuci wyjątek? W którym miejscu powinien on być obsłużony i czy powinno mieć to wpływ na wykonanie pozostałych procedur obsługi? W powyższym przykładzie wystapienie wyjątku spowoduje, że procedury znajdujące się na liście inwokacji za procedurą zgłaszającą wyjątek – nie wykonają się w ogóle.

Więc może:

Stąd już o krok od pomysłu, aby wykonywać procedury obsługi zdarzenia współbieżnie (asynchronicznie). Wystarczy zmienić d.Invoke() na d.BeginInvoke(). Czy to ma sens? W przypadku, kiedy mowa o zdarzeniach związanych z interfejsem użytkownika – na pewno nie. W innych przypadkach – może warto się zastanowić. Ma to na pewno jedną zaletę: tak jak foreach i try zapewnią wykonanie całej listy inwokacji pomimo wystąpienia wyjątku, tak wywołanie asynchroniczne zapewni wykonanie całej listy inwokacji pomimo zawieszenia się którejś z procedur.

W przypadkach, kiedy wykonujesz własne, solidnie napisane prodecury – zabezpieczanie się blokiem try powinno być zbędne. W przypadku, kiedy do twoich zdarzeń mogą się podpinać procedury z jakichś niezaufanych wtyczek – pomoże zapobiec sytuacji, w której awaria jednej wtyczki wywala całą aplikację.

Tymczasem z prostego Bang() zrobiła się całkiem skomplikowana procedura. A przecież zdarzenia miały być takie proste, wygodne… Spróbujmy to nieco poprawić, tworząc metodę rozszerzającą.

Efekt? Zamiast Bang(this, EventArgs.Empty);, trzeba napisać Bang.Raise(this, EventArgs.Empty);.

Jeszcze tylko 2 uwagi, które rozwieją wątpliwości, jakie mogą u niektórych się pojawić:
Instrukcja Bang.Raise zadziała nawet kiedy Bang==null, ponieważ Raise jest metodą statyczną, która przyjmuje Bang jako pierwszy parametr, a jedynie wygląda, jakby była metodą instancji Bang (o to właśnie chodzi w metodach rozszerzających).
W samym ciele metody zniknęła instrukcja tworząca kopię zdarzenia w zmiennej temp. Teraz operacja ta ukryta jest w przekazaniu zdarzenia jako parametr metody Raise.

2 thoughts on “Zdarzenia w C#

  1. Zamiast tyle się pocić polecam utworzenie metody ‘pustej’ i przypisanie jej do delegata w klasie publikującej zdarzenie. Tak utworzone przypisanie powoduje 100% poprawne funkcjonowanie w trybie wielowątkowym i co najlepsze zdejmuje z nas konieczność nerwowego przejmowania się, czy delegat jest null.

    Proste, więc po co się męczyć…

  2. Przecież ten artykuł porusza więcej problemów, niż tylko opróżnienie listy inkowacji, a pokazaną metodę Raise trudno nazwać męczeniem się.

Leave a Reply

Your email address will not be published. Required fields are marked *