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: