30/03/2009

.NET Remoting - podsumowanie

Home

W ostatnim czasie kilkukrotnie rozmawiałem, odpowiadałem na pytanie dotyczące przekazywania danych z serwera do klienta i w drugą stronę przy użyciu .NET Remoting. W poście tym chciałbym zebrać wszystkie informacje i przedstawić w spotaci klarownej tabelki. Dalej będę używał skrótu MBVO (ang. Marshal by value object) dla określenia obiektów klas przekazywanych przez wartość oraz MBRO (ang Marshal by reference object) dla określenia obiektów klas przekazywanych przez referencję. Klasy obiektów MBVO oznaczone są atrybutem SerializableAttribute, a klasy obiektów MBRO dziedziczą po MarshalByRefObject i ewentualnie mogą być oznaczone atrybutem SerializableAttribute ale jest to zbyteczne.



Metody obiektów MBVO wykonują się zawsze lokalnie dla wywołującego. Metody obiektów MBRO wywołują się albo lokalnie albo zdalnie dla wywołującego w zależności od tego czy pracuje od z oryginalnym obiektem czy z proxy do niego.

W przypadku obiektów MBVO zarówno klient jak i serwer muszą znać pełną definicję klasy obiektu. Serwer po to aby utworzyć obiekt i przekazać go do klienta, a klient aby odtworzyć go po swojej stronie. W przypadku obiektów MBRO nie jest to konieczne ponieważ obiekty te szeregowane są przez proxy. Aby utworzyć proxy wystarczy znajomość interfejsu. Dokładną definicję klasy musi więc znać tylko strona udostępniająca obiekt tej klasy.

25/03/2009

Synchronizacja - wydajność

Home

Niedawno opublikowałem post dotyczący prymitywów synchronizacyjnych. Przy tej okazji zostałem zapytany czemu użycie prymitywów takich jak semafor czyli implementowanych po stronie systemu operacyjnego jest wolniejsze. Odpowiedź zamieściłem w odpowiedzi do komentarz. W tym poście chciałem przedstawić jeszcze wyniki krótkiego testu jaki przeprowadziłem. Dotyczył on zbadaniu wydajności rozpatrywanych wcześniej prymitywów i polegał na wielokrotnym wchodzeniu i wychodzeniu do/z sekcji krytycznej przez dwa watki. Sekcja krytyczna była oczywiście zabezpieczonej przez te prymitywy. Do pomiaru czasu użyłem klasy Stopwatch. Wyniki są zgodne z tym co napisałem. Zamieściłem je poniżej.


20/03/2009

Inkwizycja

Home

Ostatnio po dłuższej przerwie sięgnąłem ponownie to opowiadań Jacka Piekary o Inkwizytorze Jego Ekscelencji Biskupa Hez-hezronu Mordimerze Madderdinie i pomimo, że znam już ich fabułę naprawdę nie mogłem się oderwać. Nietuzinkowy bohater, akcja, świetny warsztat pisarski, ciekawy i oryginalny świat, wciągająca fabuł i do tego nutka skandalu. Pozwolę sobie przytoczyć fragment opisu książki: "Najbardziej wstrząsająca i bluźniercza wizja w historii polskiej fantastyki!" Czego trzeba więcej? Naprawdę polecam ten cykl, na który składają sie cztery tomy opowiadań: "Sługa Boży", "Młot na czarownice", "Miecz aniołów", "Łowcy Dusz" oraz pierwszy tom dłuższej serii "Płomień i krzyż".

18/03/2009

Velocity (cz. 2)

Home

Oj długo zbierałem się do napisania tego posta chociaż po drodze pojawiło się kilka innych. Nadeszła jednak pora aby pojawił się, zresztą obiecany, post dotyczący terminologii związanej z Velocity, procesu konfigurowania i instalowania (pierwszy wpis dotyczący tej technologii znajdziemy tutaj). Dla tych, którzy zapomnieli lub nie czytali, Velocity to rozproszony cache - rozproszona pamięć podręczna. Celem Velocity, jest zwiększyć niezawodność i wydajność działania aplikacji poprzez replikację, rozproszenie oraz zapewnienie szybkiego dostępu do danych. Produkt rozwijany jest przez Microsoft i finalna jego wersja nie ujrzała jeszcze światła dziennego. Obecnie dostępna wersja to tzw. CTP2 (ang. Community Technology Preview). W poście tym nie będę opisywał wszystkiego, wgłębiał się w szczegóły bo od tego jest dokumentacja, zresztą bardzo dobra. Chciałbym napisać tylko o najważniejszych moim zdaniem elementach i skłonić do przyjrzenia się Velocity bliżej.

Przy omawianiu architektury Velocity posłużę się dwoma obrazkami, które zaczerpnąłem z dokumentu stanowiącego wprowadzenie do technologii. Znajdziemy go tutaj.



Velocity uruchamiane jest w formie kolekcji usług systemowych. W szczególności może to być pojedyncza usługa zainstalowana na jednej maszynie. Usługę taką nazywamy usługą gospodarza cache'a (ang. cache host service), a maszynę na której jest zainstalowana serwerem cache'a (ang. cache server). Zbiór takich usług nazywamy natomiast klastrem (ang. cache cluster). Aby wszystkie składowe klastra mogły się z sobą dogadać muszą mieć dostęp do wspólnej konfiguracji. Może ona zostać zapisana w bazie danych lub w pliku udostępnionym w współdzielonym katalogu. Co ważne wiele czynności o naturze administracyjnej czy też konfiguracyjnej można wykonać bez dotykania się plików konfiguracyjnych przy pomocy konsoli PowerShell. Przy instalacji Velocity rejestruje swoje własne komendy.

Monitorowaniem i zarządzaniem całego klastra zajmują się wyznaczone jego węzły tzw. główni gospodarze (ang. lead host). To ich zadaniem jest zapewnić aby klaster był cały czas dostępny, monitorowanie dostępności poszczególnych węzłów klastra czy też zapewnienie żeby dane były rozłożone równomiernie pomiędzy jego węzły. Ilość głównych gospodarzy zależy od wielkości klastra.



W obrębie klastra możemy utworzyć jeden lub więcej tzw. nazwanych cache'y (ang. named cache) lub ewentualnie skorzystać z domyślnego. Nazwany cache'y to po prostu wydzielony fragment rozproszonej pamięci, w którym można umieszczać dane. Często zamiast używać terminu nazwany cache mówi sie po prostu cache.

Można zapytać czemu warto korzystać z tej opcji skoro udostępniono cache domyślny. Otóż każdy cache nazwany jest konfigurowany oddzielnie. Konfigurowanie dotyczy natomiast tak ważnych rzecz jak to jak długo dane będą przechowywane w cache'u czy też typu cache. Dla przypomnienia, typ replikujący po umieszczeniu w nim porcji danych tworzy jej kopię i umieszcza na węzłach klastra (dobre dla danych wolno-zmiennych). Typ partycjonujący rozmieszcza dane równomiernie pomiędzy poszczególne węzły klastra (dobry dla danych szybko-zmiennych). Typ replikujący się nie jest dostępny w CTP2.

W cache'u możemy umieścić każdy serializowalny obiekt (dalej będę stosował wymiennie pojęcia obiekt/dane). Umieszczając obiekt w cache podajemy klucz, którego użyjemy następnie do jego odczytania. Istnieje również możliwość grupowania obiektów w obrębie cache'a. W nomenklaturze Velocity taką grupę obiektów nazywamy regionem (ang. region). Umożliwiają one wspólne zarządzanie większą grupą obiektów. Użycie regionów ma jednak tą wadę, że region jest przypisany do jednego gospodarza. W przypadku pojedynczych obiektów cache może je natomiast równomiernie rozłożyć pomiędzy węzły klastra. Jako podsumowanie można przyrównać nazwany cache do bazy danych. Regiony natomiast do tabel w tejże bazie.

Ale co w tym takiego specjalnego? Po pierwsze Velocity zapewnia, że jeśli umieścimy w cache'u jakieś dane to będą one dostępne z każdej maszyny, na której jest on widoczny. Nawet jeśli dane będą znajdować się fizycznie gdzie indziej, w sposób przezroczysty dla klienta zostaną odnalezione i przesłane przez sieć. Ale to nie wszystko. Velocity zapewnia również całkowicie przezroczystą dla klienta replikację danych. Możemy określić w ilu kopiach ma być przechowywana każda porcja umieszczonych w cache'u danych. Spośród wszystkich replik wyróżnia się replikę główną, na której realizowane są wszystkie aktualizacje i odczyty oraz repliki poboczne, o charakterze zapasowym.

Synchronizacja tych kopii odbywa się w sposób automatyczny i niewidoczny dla klienta! Po drugie w przypadku awarii, któregoś z węzłów z klastra i utracie dostępu do kopii głównych ich rolę przejmują kopie poboczne. Oczywiście nie można przesadzić z ilością kopii. Ich synchronizacja przecież kosztuje.

Instalacja Velocity nie jest kłopotliwa i zajmuje kilkanaście minut. W gruncie rzeczy sprowadza się do uruchomienia instalatora. W obecnej wersji automatyczna instalacja umożliwia zainstalowanie tylko jednej instancji usługi na jednej maszynie. Jeśli chodzi o konfigurowanie to wspomnę jeszcze tylko, że: konfiguracja usługi znajduje się w pliku .config w katalogu instalacyjnym Velocity na danej maszynie. Konfigurację klienta Velocity umieszczamy natomiast w pliku .config tyle, że danej aplikacji webowej lub okienkowej. Schemat konfiguracji nie jest trudny i jest dokładnie opisany w dokumentacji.

16/03/2009

TechFest 2009

Home

Możliwość uczestnictwa w konferencji TechFest 2009 bardzo mnie ucieszyła. Wiele sesji, zagraniczni uczestnicy, laboratoria no i miejsce – Hotel Gołębiewski z Wiśle. Niestety w obecnej chwili jestem daleki od zadowolenia. Czemu? Płatność zrealizowałem już ponad miesiąc temu, a od tego czasu nie dostałem potwierdzenia. Zestawu powitalnego ani widu ani słychu choć do konferencji pozostały jeszcze tylko niecałe trzy tygodnie! Kontakt z organizatorami, jaki kontakt? Brak odpowiedzi na telefony i wiadomości e-mail (no dobrze raz udało mi się dodzwonić). Dla mnie to brak kultury i dobrego wychowania oraz zwykłe lekceważenie. Mam wrażenie, że organizatorzy imprezy jeszcze długo nie będą mogli pozbyć się swoistego smrodku na jaki udało im się zapracować. Jest jeszcze szansa, że sama konferencja przyćmi dotychczasowe niepowiedzenia. Po dotychczasowych doświadczeniach jednak szczerze w to wątpię, obym się mylił.

08/03/2009

Prymitywy synchronizacyjne - jak dobrze je znamy?

Home

Ostatnio zadałem kilku osobom następujące pytanie. Spośród wymienionych poniżej prymitywów synchronizacyjnych wybierz, te które mogą zostać użyte do synchornizacji między-procesowej:
  • Klasa Monitor
  • Klasa Mutex
  • Klasa Semaphore
  • Słowo kluczowe lock
  • Klasa AutoResetEvent
  • Klasa ManualResetEvent
Sądziłem, że pytanie należy raczej do tych z kategorii łatwiejszych. Odpowiedź na nie przysporzyła jednak niestety sporo kłopotów. Na poniższej liście zostawiłem tylko elementy stanowiące poprawną odpowiedź:
  • Klasa Mutex
  • Klasa Semaphore
  • Klasa AutoResetEvent
  • Klasa ManualResetEvent
Czym różnią się wybrane prymitywy synchronizacyjne od pozostałych? Moim zdaniem można wymienić cztery zasadnicze różnice:
  • Po pierwsze klasy te dziedziczą pośrednio po klasie MarshalByRefObject i w związku z tym instancje tych klas mogą przekraczać granice po między dziedzinami aplikacyjnymi (ang. application domain).
  • Po drugie klasy te są wywiedzione z klasy WaitHandle i w związku można ich używać w podobny sposób chociaż mają zupełnie inną semantykę. W szczególności można sobie wyobrazić scenariusz, w którym wątek oczekuje równocześnie na zwolnienie semafora, mutexa i zapalenie się sygnału/zdarzenia (należy użyć metody statycznej WaitHandle.WaitAll())
  • Po trzecie klasy te stanowią w rzeczywistości opakowanie na natywne/systemowe prymitywy synchronizacyjne udostępnione w systemie operacyjny co generalnie powoduje, że są wolniejsze.
  • Po czwarte i najważniejsze z perspektywy pytania te cztery prymitywy synchronizacyjne mogą zostać nazwane. Inaczej mówiąc, w jednym procesie możemy utworzyć semafor o nazwie "MY SEMAPHORE", a w drugim procesie uzyskać dostęp do tego samego semafora posługując się podaną nazwą. W tym celu można użyć metody statycznej Semaphore.OpenExisting() lub z odpowiedniego konstruktora.
Moim zdaniem wiedza o sposobie działania, semantyce poszczególnych prymitywów synchronizacyjnych jest niezwykle ważna dla każdego programisty bez względu z jaką technologią pracuje. Błędy związane z synchronizacją są jednymi z najtrudniejszych do wykrycia i naprawienia, a wiele z nich można by unikać gdyby wszyscy programiści znali dokładnie narzędzie swojej pracy.

05/03/2009

Xceed Chart

Home

Ostatnie kilka dni w pracy spędziłem na oprogramowywaniu komponentu do rysowania wykresów firmy Xceed w wersji dla Windows Forms (jest też wersja dla ASP.NET). Wcześniej nie korzystałem z produktów tej firmy i po tych kilku dniach kontaktu z jednym z nim muszę przyznać, że pracowało mi się bardzo przyjemnie.

API nie jest bardzo trudne, a jeśli sprawia trudności zawsze można sięgnąć do bogatej biblioteki interaktywnych przykładów. Komponent wręcz przytłacza swoimi możliwościami. Mam na myśli naprawdę przeogromną liczbę opcji konfiguracyjnych. Wybierać możemy pomiędzy wykresami dwu- i trój- wymiarowymi, liniowymi, słupkowymi, radarowymi... i prawie dowolnie dostosowywać ich wygląd do swoich potrzeb. Podczas pracy komponent zachowywał się bardzo stabilnie. Jak do tej pory nie zauważyłem również problemów z wydajnością. Nadmienię jeszcze, że pracowałem z starszą wersją komponentu (4.0). Zakładam, że wersja najnowsza (4.2) musi być co najmniej tak dobra jak stara.

Jeśli ktoś potrzebuje kontrolki do rysowania wykresów to naprawdę polecam. Największy mankament to niestety cena - kilkaset dolarów. Do użytku osobistego drogo ale dla firmy nie jest to już duży wydatek.

22/02/2009

Zakamarki Visual Studio (cz. 4)

Home

W czwartym już poście dotyczącym różnych ciekawych funkcji środowiska programistycznego Visual Studio chciałbym przyjrzeć się dokładniej oknu Call Stack, a przy okazji poruszyć kilka innych tematów.

Okno Call Stack, breakpoint'y...

Czym jest breakpoint zapewne każdy programista wie, a jeśli nie to wstyd. Z drugiej strony nie jestem już taki pewny czy każdy programista zna wszystkie możliwości breakpoint'ów w Visual Studio: filtrowanie, warunki, zliczanie, uruchamiania makr czy wstawienie breakpoint'a do metody, której kodu nie znamy (o tym pisałem tutaj). Opisanie tych rzeczy to temat na oddzielny post. W tym miejscu skupmy się na użyciu pułapek w oknie Call Stack.

Po pierwsze umożliwia ono wstawianie breakpoint'ów. W tym celu wybieramy interesującą nas ramkę (metodę), zaznaczamy ją i naciskamy przycisk F9 (albo wywołujemy menu kontekstowe i wybieramy Breakpoint -> Insert Breakpoint). Breakpoint pojawi się zarówno w oknie Call Stack jak i na marginesie edytora kodu. W odwrotnym scenariuszu breakpoint postawiony w kodzie pojawi się w oknie Call Stack dopiero kiedy spowoduje zatrzymanie aplikacji. W każdej metodzie może zostać umieszczony wiele pułapek i trudno by je wszystkie umiescić w oknie Call Stack.

Przy pomocy okna Call Stack można również zarządzać pułapkami czyli określić warunek zatrzymania, filtr, wyłączyć pułapkę itd. W tym celu klikamy prawym przyciskiem wybraną pułapkę, z menu kontekstowego wybieramy Breakpoint, dalej interesującą nas komendę np.: Disable Breakpoint w celu wyłączenia pułapki lub Condition... w celu określenia warunku zatrzymania się pułapki.



Okno Call Stack umożliwia również ustawienie punktu śledzenia (ang. tracepoint). Tracepoint to w gruncie rzeczy specjalny breakpoint, który nie powoduje zatrzymania wykonania ale wypisanie komunikatu do okna Output. W celu ustawienie tracepoint z poziomu okna Call Stack wywołujemy menu kontekstowe i wybieramy Breakpoint -> Insert Tracepoint. Ustawienie punktu śledzienia z poziomu edytora kodu rozpoczyna się od ustawienia zwykłego brakpoint'a. Następnie należy wywołać menu kontekstowe, dalej wybrać When Hit... (sposób ten działa również w przypadku okna Call Stack). W oknie, które pojawi się zaznaczamy pola wyboru jak na rysunku:

Jeśli nie zaznaczymy pola wyboru Continue execution utworzymy zwykły breakpoint, który dodatkowo będzie powodował wypisanie komunikatu do okna Output. Tracepoint'y reprezentowane są przez romby:

W polu tekstowym okna When Breakpoint Is Hit możemy wpisać dowolny tekst, wyświetlić dowolną zmienną lub użyć jednego ze specjalny słów kluczowych. Ale po kolei. Aby wyświetlić zmienną należy wpisać { nazwa }. Słowa kluczowe wpisujemy natomiast po znaku $. Na przykład $PID służy do wypisania identyfikatora procesu. Nie będę omawiał wszystkich słów kluczowych ponieważ, jak widać na powyższym rysunku, ich opis można znaleźć w oknie When Breakpoint Is Hit. Zwrócę tylko jeszcze uwagę na bardzo ciekawą możliwość wyświetlenia stosu przy pomocy słowa kluczowego $CALLSTACK oraz na to, że przy tworzeniu komunikatu jesteśmy ograniczeni tylko do jednej linii.

Run To Cursor

Run To Cursor to bardzo znane polecenie, które umożliwia rozpoczęcie debugowania i wskazanie miejsca w kodzie, w którym debugger ma się zatrzymać (pod warunkiem, że nie zatrzyma się wcześniej z powodu breakpoint'a). Niewiele osób jednak wie, że polecenie to jest dostępna z poziomu okna Call Stack. Pozwala wskazać, do którego miejsca chcemy zwinąć stos. Wystarczy wybrać interesującą nas ramkę/metodę i z menu kontekstowego wybrać tę komendę.

Wartość argumentów

Inną bardzo fajną cechą okna Call Stack jest to, że podaje ono wartości przekazanych do funckji argumentów. Jeśli wartości argumentów nie są widoczne powinniśmy w onie Call Stack wywołać menu kontekstowe, a następnie zaznaczyć Show Parameter Values. No dobrze, w przypadku typów prostych jest to z pewnością przydatne ale co z typami złożonymi. Domyślnie zostanie wyświetlona po prostu nazwa typu ale można to zmienić korzystając z atrybutu DebuggerDisplayAttribute. Dla tych co nie wiedzą jest to jeden z wielu atrybutów, który pozwala na dostosowanie debuggera do naszych potrzeb (kolejny dobry temat na oddzielny post, a nawet kilka :)). Najłatwiej wyjaśnić to na przykładzie. Posłużmy się kodem jak poniżej:
public class Test
{
   private int value;

   public int Value
   {
      get { return this.value; }
   }

   public Test(int value)
   {
      this.value = value;
   }
}
Wartości typu Test zostaną przedstawione w taki o to mało interesujący sposób:

ConsoleApplication.Program.Equals(a = {ConsoleApplication.Program.Test}, b = {ConsoleApplication.Program.Test})

Wystarczy jednak zmodyfikować definicję klasy Test jak poniżej:
[DebuggerDisplay("Value = {value}")]
public class Test
{
...
}
Aby osiągnąć taki efekt:

ConsoleApplication.Program.Equals(a = Value = 1, b = Value = 1

Technika ta działa nie tylko w przypadku okna Call Stack ale również w przypadku innych okien debuggera czyli np.: Quick Watch.

Unwind To This Frame

Unwind To This Frame to jedna z komend dostępna w menu kontekstowym okna Call Stack. W większości przypadków kiedy wywołamy menu jest wyszarzona. Dostępna staje się tylko w przypadku kiedy zostanie rzucony wyjątek. W momencie rzucenia wyjątki środowisko najczęściej zatrzymuje wskazując linię w kodzie, w której wyjątek został wygenerowany (w szczególności może się nie zatrzymać jeśli wyjątek został obsłużony, a my nie mamy zaznaczonej opcji zatrzymywania na wszystkich wyjątkach). W oknie Call Stack ramka, w której został rzucony wyjątek wskazywana jest przy pomocy żółtej strzałki. Jeśli wyjątek został rzucony poza naszym kodem ostatnia ramka wskazująca nasz kod zostanie oznaczona strzałką zieloną .

Kiedy środowiska zatrzyma się już z powodu wyjątku możemy w oknie Call Stack wybrać interesującą nas ramkę, wywołać menu i wybrać komendę Unwind To This Frame. Spowoduje to zwinięcie stosu do wybranej ramki (działanie podobne do Run To Cursor) i umożliwi np.: zdiagnozowanie przyczyny wyjątku, modyfikację zmiennych lokalnych tak aby wyjątek nie został rzucony itd.

Polecenie Unwind To This Frame nie jest dostępne dla wszystkich ramek. Stos można zwinąć do każdej ramki powyżej ostatniej ramki, która wskazuje miejsce w naszym kodzie (pomiędzy żółtą i zieloną strzałką :)). Czyli jeśli wyjątek został rzucony bezpośrednio w naszym kodzie polecenie będzie dostępne tylko dla jednej ramki. Z kolei dla przypadku pokazanego niżej:

System.Xml.Serialization.XmlSerializer.Deserialize(xmlReader, encodingStyle, events)
System.Xml.Serialization.XmlSerializer.Deserialize(stream)
ConsoleTest.Program.Main(args)
...


Stos możemy zwinąć do jednej z metod Deserialize() albo do metody Main() w naszym kodzie.

Omawiając tą funkcjonalność należy koniecznie napisać o exception assistant. Całkiem możliwe, że większość użytkowników Visual Studio nigdy o nim nie słyszała. W sumie nie dziwne ponieważ jest domyślnie włączony (Tools -> Options -> Debugging -> General -> Enable the exception assistant). A do czego służy? Poniżej zamieszczam okienka wyświetlone przez środowisk w momencie rzucenia wyjątku kiedy asystent jest wyłączony (pierwszy obrazek) i kiedy jest włączony (drugi obrazek):



Mam nadzieję, że zalety asystenta są oczywiste :) ale asystent to nie tylko ładne okienko. Jeśli jest włączony w momencie rzucenia wyjątki środowisko wskaże zawsze linię w naszym kodzie nawet jeżeli prawdziwe źródło wyjątku jest gdzie indziej. Dzięki temu możemy po prostu "złapać" żółtą strzałkę, wskazującą w głównym oknie ostatnio wykonaną instrukcję, i przeciągnąć ją kilka linijek wcześniej lub kilka linijek dalej. Innymi słowy możemy ominąć wyjątek lub prześledzić jak został spowodowany. Przy włączonym asystencie polecenie Unwind To This Frame staje się trochę mniej ponieważ przydatne ale czasami możemy chcieć wyłączyć asystenta. W każdym razie dobrze zdawać sobie sprawę z jego istnienia.

Opisane techniki testowałem w środowiskach Visual Studio 2005 oraz Visual Studio 2008.

19/02/2009

Wycieki obiektów GDI

Home

Ostatnio zajmowałem się w pracy nieprzyjemnym problem z wyciekiem obiektów GDI. Sytuacja wydawała się dziwna ponieważ wyglądało na to, że metoda Dispose() była wołana wszędzie gdzie było to potrzebne. Po mówiąc szczerze dłuższym czasie dowiedziałem się o dwóch istotnych rzeczach:
  • Kontrolka TreeView zawiera bug polegający na tym, że jeżeli włączymy pokazywanie pól wyborów (ang. CheckBox) dla węzłów drzewa to zostaną zaalokowane cztery obiekty GDI. Nie zostaną one jednak zwolnione przy wywołaniu Dispose(). Ciekawą dyskusję na ten temat można znaleźć tutaj. Co gorsza jeśli poszperamy na sieci okaże się, że błąd jest znany od co najmniej kilku lat!
  • Drugi bug, o którym chciałem powiedzieć dotyczy klasy ImageList. Otóż klasa ta ma problemy ze zwalnianiem zasobów dla umieszczonych w niej obrazków/ikon, co doprowadza oczywiście do wzrostu liczby obiektów GDI. O błędzie tym możemy przeczytać na stronach firmy Microsft. Rozwiązanie, a raczej obejście problemu polega na wywołaniu metod GC.Collect() oraz GC.WaitForPendingFinalizers() po wywołaniu Dispose() dla instancji ImageList. Rozwiązanie działa ale korzystanie z metody GC.Collect() może mieć negatywny wpływ na wydajność aplikacji. Co gorsza może wystąpić jeszcze mniej pożądany efekt. Opieram się tutaj na doświadczeniach kolegi. Zgodnie z nimi używanie GC.Collect() w aplikacjach korzystających z kontrolek ActiveX może doprowadzić do ich zawieszenia w losowym momencie.
    ...
    imageList.Dispose();
    
    GC.Collect();
    GC.WaitForPendingFinalizers();
    ...
    
Z opisanych przeze mnie rzeczy trzeba zdawać sobie sprawę i być uważnym przy stosowania klasy TreeView, a w szczególności klasy ImageList. W przypadku posługiwania się TreeView można pokusić się jeszcze o użycie, zaproponowanej we wspomnianej przeze mnie dyskusji, klasy LeaklessTreeView (testowałem i wygląda, że działa).

17/02/2009

operator==

Home

Pomysł na napisanie tego posta podsunął mi kolega z pracy. Na początek fragment klasy, która we wzorcowy sposób dostarcza własnej implementacji operatora równości:
public class Test
{
  private int i;
  
  public Test(int i)
  {
    this.i = i;
  }

  public static bool operator==(Test a, Test b)
  {
    if (System.Object.ReferenceEquals(a, b))
      return true;

    if (((object)a == null) || ((object)b == null))
      return false;

    return a.i == b.i;
  }
  
  ...
}
Oczywiście potrzebna jest jeszcze implementacja operatora nierówności !=. Chciałbym też zwrócić uwagę, ponieważ sam zapomniałem o tym pisząc ten przykład :), że implementując operator równości (czy też nierówności) kiedy wykonujemy porównanie takie jak a == b, a == null czy b == null musimy zrzutować testowane obiekty do typu object. W innym wypadku nastąpi rekurencyjne wywołanie naszej implementacji operatora. Rzutowanie zapewni, że zostanie wywołana implementacja bazowa - operatory nie działają jak metody wirtualne. Inne rozwiązanie to użycie metody System.Object.ReferenceEquals. Teraz popatrzmy na kod poniżej i odpowiedzmy sobie na pytanie jaką wartość przyjmą zmienne lokalne res oraz res2:
public class Program
{
  public static bool MyEquals(T a, T b) where T : class
  {
    return a == b;
  }
  
  public static void Main(string[] args)
  {
    Test a = new Test(1);
    Test b = new Test(1);

    bool res = a == b;
    bool res2 = MyEquals(a, b);
  }
}
Odpowiedź brzmi oczywiście true oraz false. Piszę o tym również dla siebie ponieważ sam musiałem się zastanowić jaki będzie wynik. Pierwszy rezultat jest chyba oczywisty i nie wymaga żadnych tłumaczeń. Po prostu wywołany zostanie zaimplementowany przez nas operator. A co z wywołaniem metody generycznej MyEquals. Może się wydawać, że skoro jest to metoda generyczna to zostanie wywołany operator równości dla typu przekazanego jako argument typu generycznego (ang. generic type parameters). Argument typu generycznego to to co podstawiamy pod T czyli w tym przypadku Test.

Jest jednak inaczej. W momencie kompilacji kompilator nie wie co zostanie podstawione pod T. W związku z tym generowany jest kod sprawdzający równość referencyjną. Na poziomie IL oznacza to użycie instrukcji ceq. Jeśli zmienimy kod metody MyEquals tak jak poniżej kompilator będzie już mógł wybrać operator równości dostarczony przez nas zamiast bazowego.
public static bool MyEquals(T a, T b) where T : Test
{
  return a == b;
}

04/02/2009

Velocity (cz. 1)

Home

Velocity (z angielskiego prędkość) to mało znany projekt firmy Microsoft, którego celem jest stworzenie rozproszonej pamięci podręcznej czyli rozproszonego cache'a (nie należy mylić z projektem fundacji Apache, którego celem jest opracowanie procesora szablonów). Innymi słowy Velocity ma zwiększyć niezawodność i wydajność działania aplikacji poprzez replikację, rozproszenie oraz zapewnienie szybkiego dostępu do danych. Obecnie dostępna wersja produktu to dopiero tzw. CTP2 (ang. Community Technology Preview) czyli wersja udostępniona społecznością w celach testowych, zgłaszania uwag oraz postulatów. Nie wiadomo kiedy dokładnie pojawi się końcowy produkt ale moim zdaniem już teraz Velocity jest godne zainteresowania, a postem tym chciałbym rozpocząć krótki cykl trzech może czterech wpisów dotyczących tej technologii.

Na początek warto wymienić najważniejsze (moim zdaniem) rzeczy charakteryzujące Velocity. Zestaw ten oparłem na własnych doświadczeniach oraz na tym czego dowiedziałem się z dokumentacji i artykułów.
  • W pamięci podręcznej może zostać umieszczony każdy serializowalny obiekt.
  • Prosty interfejs programistyczny.
  • Wspiera PHP, C#, C++...
  • Możliwość wykorzystania w aplikacjach stacjonarnych, konsolowych i webowych. Przewidziana jest też integracja z dostawcą sesji ASP.NET.
  • Możliwość zarządzania i konfigurowania pomocy konsoli PowerShell.
  • Możliwość uruchomienia klastra złożonego z kilku, a nawet kilkunastu maszyn. Konfiguracja klastra może znajdować się w bazie danych lub w pliku konfiguracyjnym umieszczonym w współdzielonym katalogu.
  • Kilka typów cache'a: replikujący, partycjonujący. Typ replikujący po umieszczeniu w nim porcji danych tworzy jej kopię i umieszcza na węzłach klastra (dobre dla danych wolno-zmiennych). Typ partycjonujący rozmieszcza dane równomiernie pomiędzy poszczególne węzły klastra (dobry dla danych szybko-zmiennych). W wersji CTP2 dostępny jest tylko typ partycjonujący. Dane w cache'u partycjonującym również mogą być replikowane w celu zwiększenia bezpieczeństwa na wypadek awarii (kopia główna i poboczne).
  • Velocity wspiera silne i słaby model spójności dostępu do danych (w zależności od typu cache'a). Model spójności to umowa pomiędzy składnicą danych, a klientem mówiąca, że jeżeli klient będzie postępował zgodnie z określonymi regułami to dane nie ulegną uszkodzeniu. Słaby model spójności bazuje na jawnych zmiennych synchronizacyjnych. Silne modele spójności są implementowane przezroczyście dla klienta.
  • Operacje replikacji, partycjonowania danych, a także synchronizowania ich kopii są w pełni automatyczne i przezroczyste dla klienta.
  • Dostępne podejście optymistyczne (oparte o znaczniki, kto pierwszy ten lepszy) i pesymistyczne (wykorzystujące jawne operacje synchronizacyjne) do synchronizowania współbieżnych dostępów do danych.
  • Możliwość dynamicznego rekonfigurowania klastra poprzez dodawanie nowych węzłów.
  • Automatyczne rekonfigurowanie klastra w wypadku awarii.
  • Automatyczne równoważenie obciążenia węzłów klastra.
  • Velocity może zostać uruchomione jako usługa systemowa lub działać w procesie aplikacji.
  • Można konfigurować ilość pamięci zużywanej przez Velocity.
Osobiście pracowałem z Velocity zbyt krótko aby potwierdzić wszystkie te cechy. W szczególności testowałem tylko klaster złożony z kilku komputerów. Nie wiem też jak sprawuje się dynamiczna rekonfiguracja klastra i automatyczne równoważenie obciążenia.

W następnym poście przedstawię terminologię związaną z Velocity: gospodarz, główny gospodarz, nazwany cache itd. oraz wspomnę trochę o procesie instalowania i konfigurowania. W trzecim poście zajmę się API. Powiem również o wadach i rzeczach, które powinny moim zdaniem zostać zmienione w wersji finalnej. Dla niecierpliwych i żądnych wiedzy garść linków:

03/02/2009

Zakamarki Visual Studio (cz. 3)

Home

W trzecim już poście dotyczącym zakamarków Visual Studio, czyli mało znanych funkcji tego środowiska programistycznego, chciałbym napisać o oknie Object Test Bench. Okno to pozwala na szybkie testowanie wywołań metod statycznych i instancyjnych bez potrzeby pisania małych programików testowych. Jeśli po uruchomieniu środowiska nie jest widoczne możemy je wyświetlić wybierając View->Other Widnows->Object Test Bench. Aby wygodnie pracować z tym oknem przyda się również okno Class View (View->Class View), które służy do przeglądania typów zdefiniowanych w naszych projektach oraz w innych pakietach/podzespołach (ang. assembly), do których się odwołujemy. Ważna uwaga! Widok Object Test Bench może zostać użyty tylko i wyłącznie do testowania klas w projekcie oznaczonym jako StartUp oraz w pakietach, do których ten projekt się odwołuje. Co prawda zgodnie z dokumentacją powinniśmy móc testować wszystkie klasy w bieżącym rozwiązaniu (ang. solution) ale niestety nie jest to możliwe.

Dalej będę posługiwał się prościutką, a wręcz naiwną klasą zdefiniowaną poniżej, w celu omówienia zastosowania okna Object Test Bench.
public class Person
{
 private static int count = 0;
 
 private string name;
 private DateTime dateOfBirth;

 public string Name
 {
  get { return name; }
  set { name = value; }
 }

 public DateTime DateOfBirth
 {
  get { return dateOfBirth; }
  set { dateOfBirth = value; }
 }

 public Person()
 {
  Person.count++;
 }

 public Person(string name, DateTime dateOfBirth)
 : this()
 {
  this.name = name;
  this.dateOfBirth = dateOfBirth;
 }

 public int GetAge(bool inDays)
 {
  if(!inDays)
   return DateTime.Now.Year - dateOfBirth.Year;
  else
   return (DateTime.Now - dateOfBirth).Days ;
 }

 public static int GetCount()
 {
  return Person.count;
 }
}
Aby rozpocząć testowanie jakiejś klasy musimy zlokalizować ją w oknie Class View i wywołać dla niej menu kontekstowe. W menu kontekstowym powinny być widoczne dwie komendy:
  • Create Instance - Pozwala wywołać dowolny konstruktor dla danego typu i przypisać mu identyfikator. Stworzony obiekt pojawi się w oknie Object Test Bench. Dla stworzonych obiektów możemy następnie wywoływać metody instancyjne. W ten sposób nie powołamy do życia instancji klasy abstrakcyjnej czy też delegata.
  • Invoke Static Mehtod - Pozwala wywołać wybraną metodę statyczną danej klasy (również klasy abstrakcyjnej).
Polecenia te będą widoczne tylko dla klas z projektu głównego. Oczywiście przed wywołaniem metody statycznej, instancyjnej czy też konstruktora powinniśmy w interesujących nas miejscach w kodzie ustawić breakpointy.

Zacznijmy od wywołania metody statycznej. Po najechaniu w menu kontekstowym na polecenie Invoke Static Mehtod wyświetlone zostaną wszystkie metody statyczne dostępne w tej klasie. Należy wybrać jedną z nich. W przypadku naszej klasy testowej Person możemy wybierać pomiędzy GetCount() oraz dwie metodami odziedziczonymi po klasie Object: Equals(object, object) oraz ReferenceEquals(object, object). Po wybraniu GetCount() pojawi się okno potwierdzające chęć wywołania tej metody. Natomiast w przypadku metod, które posiadają parametry pojawi się okno pozwalające na wyspecyfikowanie wartości tych parametrów. Kiedy klikniemy Ok metoda zostanie wywołana. Na zakończenie pojawi się okno z wynikiem, które powinno wyglądać jak poniżej: Istnieje również możliwość zapisania wyniku wywołania metody. W tym celu powinniśmy zaznaczyć opcję Save return value (w oknie pokazanym powyżej) i nadać identyfikator dla zwracanego obiektu/struktury. Jeśli zdecydujemy się na zapisanie wyniku pojawi się on w oknie Object Test Bench: Stworzone obiekty reprezentowane są w oknie Object Test Bench przez prostokąty. Dla każdego obiektu możemy wywołać menu kontekstowe, które pozwoli nam wybrać metodę instancyjną do wywołania. Spróbujmy wykonać metodę posiadającą parametry np.: CompareTo(int). Po jej wybraniu powinniśmy zobaczyć okno jak poniżej: Warto zauważyć, że jeżeli metoda opatrzona była komentarze to zostanie on przytoczony. Po podaniu wartości parametru aktualnego i wybraniu przycisku Ok, metoda zostanie wywołana, a wynik zostanie przedstawiony w takim samym oknie jak przy wywoływaniu metody statycznej (jego też możemy zapisać).

Przyjrzyjmy się teraz poleceniu Create Instance i stwórzmy instancję klasy Person korzystając z konstruktora dwuargumentowego. Po wybraniu konstruktora powinno pojawić się okno przedstawionej dalej: Po pierwsze powinniśmy podać nazwę dla tworzonego obiektu. W tym miejscu trzeba uważać bo jeśli podanym nazwę, której już użyliśmy zostaniemy o tym poinformowani dopiero po naciśnięciu przycisku Ok, a nie ma możliwości cofnięcia się i naprawienia błędu.

Po drugie, podobnie jak przy wywoływaniu metod, należy podać wartości poszczególnych argumentów. Napiszę o tym troszeczkę więcej. Jeśli chodzi o typy proste nie ma co się rozpisywać. Po prostu wpisujemy liczbę lub ciąg znaków (zamknięty w cudzysłowy) i koniec. Troszeczkę trudniej jest z typami złożonymi. Na rysunku widać, że dla parametru dateOfBirth wstawiłem dziwną wartość dateTime1. Niestety ale w przypadku typów złożonych musimy najpierw stworzyć instancję danego typu, a dopiero potem powołując się na przypisaną danej instancji nazwę, użyć jej jako argumentu wywołania. Nazwa stworzonej instancji pojawi się na rozwijanej liście dla argumentów o odpowiednim typie.

W tym przypadku stworzyłem uprzednio strukturę typu DateTime i nadałem jej nazwę dateTime1. Zacząłem od zlokalizowania typu struktury w drzewie (najwygodniej posłużyć się przyciskiem Search u góry okna Class View). Następnie wybrałem polecenie Create Instance, dalej zdecydowałem się na konstruktor trójargumentowy, jako argumenty wywołania podając rok, miesiąc i dzień mojego urodzenia (wszystkie wartości to typy proste). Na koniec powróciłem do klasy Person i ponownie wybrałem interesujący mnie konstruktor. Jak widać tworzenie obiektów wymagających skomplikowanej inicjalizacji jest trudne i kłopotliwe ale te okno nie do tego służy.

Po stworzeniu instancji klasy Person możemy przystąpić do testowania jej metod, a właściwie jednej metody. Odbywa się to dokładnie w ten sam sposób jak opisałem na przykładzie metody CompareTo(int). Polecam spróbować wywołać metodę GetAge(bool) z parametrem true oraz false oraz sprawdzić jaki wynik zwraca metoda statyczna GetCount() po utworzeniu instancji klasy Person.

Jeszcze dwie uwagi na koniec. Jeśli wprowadzimy jakieś zmiany do kodu to okno Object Test Bench wymusi na nas ponowną kompilację źródeł. Równoznaczne jest to z utratą wszystkich obiektów, struktur, które umieściliśmy wcześniej w tym oknie.

W drugiej części tej serii postów pisałem o oknie Immediate. W ciekawy sposób współpracuje ono z oknem Object Test Bench. Dla przypomnienia w oknie Immediate również możemy wywoływać metody i tworzyć obiekty. Istotne jest to, że obiekty, które stworzymy w tym oknie pojawią się również w oknie Object Test Bench. Tutaj też obowiązuje warunek, żeby użyty typ pochodził z projektu oznaczonego jako StartUp lub z pakietu/podzespołu, do którego tej projekt się odwołuje. Kolejny warunek jaki musimy spełnić (wygląda to na błąd w Visual Studio) jest taki, że okno Object Test Bench nie może być puste w momencie tworzenia obiektu w oknie Immediate. Jeśli będzie tworzone obiekty nie zostaną do niego dodane. Co ciekawe jeśli w pewnym momencie dodamy jakiś obiekt do okna Object Test Bench (z poziomu okna Class View) to nagle pojawią się w nim również obiekty utworzone wcześniej w oknie Immediate.

Warto jeszcze dodać, że obiekty z okna Immediate w oknie Object Test Bench będą miały takie same identyfikatory jak zmienne użyta w oknie Immediate. Czyli wpisanie takiej komendy:
Person person = new Person();
spowoduje dodanie do okna Object Test Bench obiektu o nazwie person

Opisane techniki testowałem w środowiskach Visual Studio 2005 oraz Visual Studio 2008.

01/02/2009

Awaria Google (część 3)

Home

I wszystko jasne. Zgodnie z komunikatem od Google, awarii zawinił pracownik, który przez przypadek zablokował wszystkie strony zamiast tylko tych z listy stron niebezpiecznych, które Google otrzymuje od serwisu StopBadware.org. Czyli jak się często zdarza najsłabszym ogniwem jest czynnik ludzki. Nie jestem zwolennikiem spiskowej teorii dziejów, więc takie wytłumaczenie jest dla mnie przekonujące. Zresztą czas to zweryfikuje. Przy tej okazji przypomniały mi się dwie zasłyszane kiedyś historie w tym klimacie.

Dla ustalenia uwagi wyobraźmy sobie niezwykle ważny dla jakiejś firmy serwer. Na serwerze przechowywane są bardzo istotne dane, musi on być dostępny on-line 24 godziny na dobę, chroniony jest najnowszym oprogramowaniem antywirusowym itd. Ale jak w każdej firmie ktoś musi odkurzać, wynosić śmiecie, zmywać podłogi, również w serwerowni. Nie ulega wątpliwości, że leżące na ziemi kable zasilająca mogą utrudnić zadanie sprzątaczce czy też sprzątaczowi. Co "powinien", więc zrobić wykonujący skrupulatnie i dokładnie swoją pracę konserwator powierzchni płaskich? ... Od tego czasu we wspomnianej firmie kable zasilające są na stałe przymocowane do gniazdek.

Druga historia dotyczy pewnego administratora sieci, który jako wygaszacz ekranu na serwerze postanowił użyć słynnego "blue screen". Niestety ale zdarzyło się, że koło serwera przechodził pewien menadżer. Kiedy zobaczył wygaszacz ekranu postanowił wyręczyć administratora w jego pracy i zrestartował serwer. Dodam, że nagły restart tego serwera nie był bez znaczenia dla firmy i jego klientów. Ciekawe kto stracił pracę?

Podsumowując, bez względu na to jakich zabezpieczeń użyjemy, zawsze zapomnimy o jakimś małym szczególe lub nie uwzględnimy czegoś co będzie wydawać się nam absurdalnie niemożliwe, a co doprowadzi do mniejszej lub większej katastrofy.

Swoją drogą zastanawialiście się kiedyś jak to jest, że jeżeli pracownicy danej firmy mają ograniczony dostęp do poszczególnych pomieszczeń to sprzątaczka czy sprzątacz mogą wejść wszędzie (lub prawie wszędzie). No chyba, że prezes będzie sprzątać swoje biuro samemu ;)

31/01/2009

Awaria Google (część 2)

Home

Chłopcy z Google szybko rozwiązali problem. "Awaria" trwała tylko, a może aż, małe kilkanaście minut. Jak widać publikuję tego posta jakieś 10 minut po poście, w którym stwierdziłem, że z serwerami Google mogło sie coś stać. Jestem ciekawy czy zostanie opublikowany oficjalny komunikat wyjaśniający cała sytuację.

Awaria Google

Home

Mam wrażenie, że z serwerami Google stało sie coś niedobrego. Jeszcze kilkanaście minut wyszukiwarka działa bardzo dobrze. W tej chwili chyba dla każdego hasła (łącznie z takimi jak: Google, Microsoft) wyszukane strony oznaczone są jako niebezpieczne; do każdej strony dodawany jest komunikat Ta witryna może wyrządzić szkody na Twoim komputerze. Wynik zaobserwowałem na dwóch komputerach. A może Google padło ofiarą ataku hackerów?

26/01/2009

Environment.CurrentDirectory

Home

Właściwość Environment.CurrentDirectory jest zapewne znana każdemu programiście .Net (przy jej pomocy odczytujemy ścieżkę do bieżącego katalogu roboczego). Co już może nie jest tak bardzo oczywiste właściwość ta posiada setter i może być dowolnie modyfikowana. Jest to ważne jeżeli używamy w naszych projektach ścieżek względnych np.:
./WorkingDir/Temp/log.txt
W takim wypadku aby określić ścieżkę bezwzględną platforma odczytuje właściwość Environment.CurrentDirectory. I tutaj mogą pojawić się problemy. Po pierwsze jeśli to my zmodyfikujemy tą właściwość możemy nieświadomie doprowadzić do wystąpienia błędu gdzieś w innej części projektu. Na przykład plik, do którego została podana ścieżka względna nie zostanie odnaleziony. Sytuacja może być oczywiście odwrotna. Żeby nie szukać daleko, użycie klasy OpenFileDialog spowoduje zmianę katalogu roboczego jeśli po wybraniu pliku użytkownik zatwierdzi swój wybór.
OpenFileDialog ofd = new OpenFileDialog();
ofd.ShowDialog();
Jeśli teraz, po wybraniu pliku przy pomocy klasy OpenFileDialog, spróbujemy uzyskać dostęp do innego pliku, do którego znamy tylko ścieżkę względna operacja zakończy się niepowodzeniem (chyba, że katalog wybrany przez użytkownika okaże się taki sam jak pierwotny katalog roboczy). Używanie ścieżek bezwzględnych jest dobrym rozwiązaniem ale nie zawsze zadziała. Co jeśli ścieżka wczytywana jest z pliku konfiguracyjnego, który może zmienić użytkownik? Wytłumaczenie klientowi żeby nie stosował ścieżek względnych nie jest dobrym pomysłem bo co złego może być w ścieżkach względnych z perspektywy klienta :).

Moim zdaniem należy więc: używać ścieżek bezwzględnych wszędzie tam gdzie jest to możliwe lub też nie bazować na wartości Environment.CurrentDirectory. Mam tu na myśli sytuację, w której używamy ścieżek względnych ale zamieniamy je na bezwzględne korzystając z jakiegoś parametru konfiguracyjnego. Można też pomyśleć o wykrywaniu ścieżek względnych podawanych przez użytkownika i zamianie ich na ścieżki bezwzględne.

Po drugie należy unikać modyfikowania właściwości Environment.CurrentDirectory, a jeśli właściwość ta zostanie zmieniona to przywrócić jej pierwotną wartość chyba, że jej modyfikacja była w 100% zamierzona.

Przyda się też wiedza o tym, że katalog roboczy może zostać zmieniony niejako za naszymi plecami oraz, że właściwość Environment.CurrentDirectory jest używana przez platformę .Net w celu określenia ścieżek bezwzględnych.

24/01/2009

Przerażająca 25

Home

Na stronie CWE pojawiła się bardzo świeże opracowanie opisujące 25 najniebezpieczniejszych i najczęściej spotykanych błędów programistycznych (po szczegóły zapraszam tutaj). Najniebezpieczniejszych w tym sensie, że zwiększających podatność oprogramowanie na wszelkiego rodzaju ataki. Zestawienie zostało stworzone przy współpracy kilkudziesięciu firm z Europy i Stanów Zjednoczonych. Na stronie znajdziemy również opis kilkuset innych błędów wraz dokładnym wyjaśnieniem i wskazówkami jak ich unikać. Naprawdę polecam. Martwi to, że spora część z wymienionych błędów jest dobrze znana od bardzo, bardzo dawna, a więc wydawałoby się, że programiści powinni być świadomi tych zagrożeń. Niektóre z błędów to wręcz przykłady akademickie: brak walidacji danych wejściowych, wstrzykiwanie SQL'a!

18/01/2009

Konsole są fajne :)

Home

Jeszcze jakiś czas temu w ogólnie nie myślałem o kupnie konsoli. Do czasu. Wczoraj zdarzyło mi się zagrać w kilka tytułów na Playstation 3. Szczególnie dobrze bawiłem się w Guita Hero. Dla niewtajemniczonych, to gra rytmiczna, w której można wcielić się w rockmana. Do gry potrzebna jest kontroler w postaci gitary, na którym w odpowiednich momentach trzeba naciskać przyciski i szarpać strony, a dokładniej coś co symuluje strunę :). Naprawdę trudno to opisać ale zabawa jest przednia. Jeszcze lepszy od gitary jest kontroler w postaci perkusji. To jest dopiero frajda. Dwie pałeczki w dłoni i walisz ile sił wlezie. Tak na marginesie seria gier Guitar Hero zarobiła ponad miliardów dolarów. Na blaszakach takich gier po prostu nie ma. W konsoli spodobało mi się jeszcze to, że wystarczy włożyć płytkę z grą napędu i można zacząć grać. Wszystko wczytuje się w maksymalnie kilkanaście sekund. I jeszcze jedna fajna rzecz. Jest dużo gier, w których można grać w dwie osoby. Na przykład w LittleBigPlanet chodzi o to aby z jednej strony zdobyć więcej punktów niż przeciwnik ale równocześnie trzeba z nim współpracować aby ukończyć planszę z powodzeniem. Jak dla mnie pomysł jest super. Zresztą wybór tytułów jest ogromny, szkoda tylko, że średnia cena to około 200 zł. Z drugiej strony konsolę kupujesz raz na kilka lat. Takie Playstation 2 jest na rynku chyba z 10. Teraz muszę się tylko zdecydować na Playstation 3 lub Xbox'a.

12/01/2009

Czemu zdarzenia nie działają ??? (część 2)

Home

W poście Czemu zdarzenia nie działają??? opisałem błąd, który doprowadza do sytuacji, w której nie otrzymujemy zdarzeń od dynamicznie tworzonych kontrolek. Jego rozwiązanie nie jest specjalnie skomplikowane i dla przypomnienia przytoczę je tutaj:

W przypadku dynamicznego tworzenia kontrolek, czy to bezpośrednio czy to przy okazji użycia kontrolek data bound zawsze należy pamiętać aby dynamiczne kontrolki były tworzone nie tylko przy inicjalnym odwołaniu do strony ale również przy kolejnych.

W tym poście chciałbym jeszcze zwrócić uwagę gdzie powinny być tworzone dynamiczne kontrolki. Najlepszym miejsce jest zdarzenie PreInit strony. Powodów jest kilka, a najważniejsze to:
  • Zdarzenie PreInit jest odpalane najwcześniej w cyklu życia strony. Jeśli stworzymy kontrolki w tym miejscu będą one dostępne w każdym kolejnym zdarzeniu - kontrolki dynamiczne będą zachowywały się tak samo jak kontrolki statycznie osadzone na stronie. To jest pierwszy i najważniejszy powód, a wszystkie wymienione dalej stanowią jego rozwinięcie.
  • Zdarzenie PreInit to jedyne miejsce, w którym możemy dynamicznie ustawić właściwość Theme. Jeśli stworzymy nasze kontrolki później to wartość tej właściwości nie będzie miała wpływu na sposób ich prezentacji.
  • Będziemy mieli zagwarantowane, że właściwości kontrolek zostaną odtworzone na podstawie ViewState.
  • Będziemy mieli pewność, że jeżeli dla dynamicznie stworzonej kontrolki ma zostać wygenerowane zdarzenie to zostanie. Innymi słowy będziemy mieli pewność, że w punkcie, w którym maszyneria ASP.NET określa dla jakiej kontrolki wywołać jakie zdarzenie, ta kontrolka będzie istnieć.
  • Takie są zalecenia :)

06/01/2009

ASP.NET Caching

Home

W poście tym chciałbym napisać kilka słów o unieważnianiu zawartości cache'a w aplikacjach ASP.NET na podstawie aktualizacji danych przechowywanych w bazie danych. Post ten nie jest jednak przewodnikiem na temat cache'owania w aplikacjach ASP.NET, opisuję w nim kilka moim zdaniem ważnych elementów. Post ten można traktować jako punkt wyjścia do dalszej nauki.

Idea jest bardzo prosta. W celu przyspieszenia aplikacji umieszczamy w cache'u jakieś dane np.: wynik zapytania czy też wyrenderowaną stronę (przy pomocy dyrektywy @OutputCache). Chcielibyśmy jednak aby w przypadku jeśli dane, na podstawie których zostało wykonane zapytanie, ulegną zmianie, ponownie wykonać zapytanie zamiast wczytywać jego wynik z cache'a. W aplikacjach ASP.NET możemy wybierać z dwóch możliwości. Jedna to obiekt klasy Cache dostępny przez właściwość strony o takiej samej nazwie. Dane umieszczone w tym pojemniku mogą zostać usunięte po upływie określonego czasu, kiedy zmianie uległ wskazy plik czy też (co interesuje nas w tej chwili) kiedy dane w bazie danych zostały zmienione. Po drugie mamy dostęp do cache'a przechowującego wyrenderowane strony czy też ich fragmenty. Z tego cache'a nie korzystamy bezpośrednio ale przy pomocy dyrektywy @OutputCache. W tym przypadku również możemy wybierać pomiędzy kilkoma wariantami unieważniania zawartości cache'a.

Przejdźmy do konkretów. W obu przypadkach aby powiązać dane w cache'u z danymi w bazie danych używamy klasy SqlCacheDependency. W przypadku cache'a dostępnego z poziomu strony musimy jawnie utworzyć instancję tej klasy. Przykład użycia znajdziemy tutaj. Natomiast w przypadku dyrektywy @OutputCache musimy użyć atrybutu SqlDependency.

Istotne jest co dzieje się pod spodem. SqlCacheDependency używa klasy SqlDependency (nazwa klasy jest taka sama jak nazwa wspomnianego wcześniej atrybutu). SqlDependency to z kolei wysoko poziomowa nakładka na inną klasę SqlNotificationRequest. Ważne jest to, że SqlDependency i SqlNotificationRequest współpracują tylko z bazą danych Sql Server 2005 i nowszymi, które dostarczają usługi powiadomień na poziomie wierszy - query notifications. To znaczy, że możemy wskazać wierszy, w przypadku zmiany których dane z cache'a zostaną usunięte. W przypadku starszych baz danych (lub nowych jeśli nie odpowiada nam query notifications) SqlCacheDependency wykorzystuje mechanizm monitorowania całych tabel zamiast wskazanej grupy wierszy. Jest to ogólnie rozwiązanie mniej wydajne.

Jeszcze parę słów o @OutputCache. Jak napisałem wcześniej aby użyć unieważniania na podstawie bazy danych należy wysterować atrybut SqlDependency. Składnia tego atrybutu jest następująca:
<%@OutputCache
...
SqlDependency="database/table name pair | CommandNotification"
...
%>
W przypadku podania pary nazwa bazy danych i nazwa tabeli zostanie użyte monitorowanie na poziomie całych tabel. W przypadku podania wartości CommandNotification zostanie użyte powiadamianie na poziomie wierszy, a dokładniej ASP.NET użyje wszystkich komend użytych w kontekście danej strony do utworzenia zależności pomiędzy danymi w cache'u, a danymi w bazie danych.

W tym momencie należy jeszcze zaznaczyć, że o ile klasa SqlCacheDependency jest właściwa dla aplikacji ASP.NET to SqlDependency i SqlNotificationRequest mogą być użyte również w innych przypadkach. W ogólności użycie SqlNotificationRequest jest raczej trudne i pracochłonne. W większości przypadków używa się SqlDependency. Przykład użycia tej klasy można znaleźć tutaj tutaj.