Singleton a wielowątkowość

Z racji tego, że w ostatnim czasie sporo pisałem o wielowątkowości w C#, dzisiaj pokaże prawidłową implementacje wzorca projektowego singleton przystosowanego do pracy w środowisku współbieżnym. Na początek przyjrzyjmy się klasycznej implementacji:

public sealed class Singleton
{
    private static Singleton m_Instance = null;
    
    private Singleton() { }
    
    public static Singleton Instance
    {
        get
        {
            if(m_Instance == null)
                m_Instance = new Signleton();
            return m_Instance;
        }
    }
}

Wyobraźmy sobie sytuację w której dwa wątki jednocześnie próbują utworzyć instancję klasy. Może zdarzyć się, że wątki jednocześnie wejdą do if’a sprawdzającego m_Instance. W takim przypadku zostaną utworzone dwie instancje Signleton. Jak temu zaradzić?

Wystarczy użyć jednego z mechanizmów synchronizacji opisanych we wcześniejszych postach. Polecam lock – w przypadku synchronizacji wewnątrz AppDomain jest to przeważnie najlepsze rozwiązanie.

public sealed class Singleton
{
    private static Singleton m_Instance = null;
    private static readonly object m_Sync = new object();
    
    private Singleton() { }
    
    public static Singleton Instance
    {
        get
        {
            lock(m_Sync)
            {
                if(m_Instance == null)
                    m_Instance = new Signleton();
            }
            return m_Instance;
        }
    }
}

Powyższa implementacja jest poprawna, jednak ma jedną wadę – za każdym razem trzeba zakładać blokadę co w środowisku rozproszonym może spowodować istotny spadek wydajności. Spróbujmy więc zakładać blokadę wyłącznie gdy m_Instance jest równy null (czyli podczas pierwszego wywołania właściwości Instance):

public sealed class Singleton
{
    private static Singleton m_Instance = null;
    private static readonly object m_Sync = new object();
    
    private Singleton() { }
    
    public static Singleton Instance
    {
        get
        {
            if(m_Instance == null )
            {
                lock(m_Sync)
                {
                    if(m_Instance == null)
                        m_Instance = new Signleton();
                }
            }
            return m_Instance;
        }
    }
}

Z kodu widzimy, że synchronizujemy kod tylko gdy instancja nie została jeszcze utworzona. Po zainicjowaniu obiektu kod działa już w pełni optymalnie, zwracając po prostu obiekt Singleton.

Jeśli nie zależy nam na tzw. lazy loading(opóźnione ładowanie) możemy stworzyć instancję obiektu w momencie deklaracji m_Instance:

public sealed class Singleton
{
    private static Singleton m_Instance = new Singleton();
    
    private Singleton() { }
    
    public static Singleton Instance
    {
        get
        {            
            return m_Instance;
        }
    }
}

Implementacja jest w pełni optymalna oraz bezpieczna w środowisku współbieżnym. Jedyną wadą jest fakt, że Signleton będzie utworzony(podczas np. dostępu do innych statycznych pól klasy) nawet w przypadku gdy  z niego nie będziemy korzystać w programie. Jeśli wiemy, że za każdym razem podczas działania aplikacji będziemy wywoływać klasę to powyższe rozwiązanie może okazać się bardzo dobre.

Czas na najlepsze moim zdaniem rozwiązanie:

public sealed class Singleton
{   
   private Singleton() { }

   public static Singleton Instance
   {
       get
       {
           return Child.m_Instance;
       }
   }
   public static int TestValue { get; set; }
   class Child
   {
       private Child() { }
       internal static readonly Singleton m_Instance = new Singleton();
   }        
}

Przedstawiona implementacja jest optymalna ponieważ nie zakładamy żadnych blokad. Ponadto jest w pełni “leniwa”- inicjujemy obiekt dopiero gdy chcemy mieć do niego dostęp. Jeśli przypiszemy jakąś wartość właściwości TestValue, obiekt Singleton  nie zostanie utworzony – w przypadku wcześniejszej implementacji jakikolwiek dostęp do statycznych właściwośći powodował inicjalizację Singleton. Rozwiązanie jest oczywiście bezpieczne w środowisku współbieżnym.

Wielowątkowość(pętle, Task) w C# 4.0

Programowanie współbieżne w c# 4.0 jest znacznie łatwiejsze w porównaniu z poprzednią wersją. Widać, że platforma .NET staje się coraz dogodniejszym środowiskiem programistycznym dla rozwiązań równoległych.

Zacznijmy od pętli foreach. Przeważnie wykonujemy ją w sposób sekwencyjny. Jeśli chcielibyśmy zrównoleglić ją, musielibyśmy stworzyć instancję Thread i zawartość pętli umieścić w wątkach. Ponadto proces wymagałby użycia np. semafora albo ManualResetEvent aby zsynchronizować kod wykonywany po zakończeniu pętli. Na szczęście w wersji 4.0 mamy do dyspozycji gotowy mechanizm:

string []tab=new string[]{"a","b","c","d","e"};
System.Threading.Tasks.Parallel.ForEach<string>(tab, Method);

gdzie Method:

private static void Method(string val)
{
}

Pętla zostanie wykonana w sposób równoległy. Jeśli tylko nie współdzielimy jakiego stanu pomiędzy kolejnymi iteracjami, warto zastanowić się nad tym typem pętli,  ponieważ jest to bardzo proste a na procesorach wielordzeniowych wzrost wydajności może być znaczący.

Nową klasą w C# 4.0 jest System.Threading.Tasks.Task. Umożliwia ona wykonanie operacji w tle(w osobnym wątku). Warto podkreślić, że Task bazuje na ulepszonym ThreadPool, zatem wydajność tworzenia nowego Task’a jest dużo wyższa niż w przypadku “ciężkiego” Thread. Samo stworzenie Task’a jest analogiczne do Thread:

  Task t1 = new Task(() => Console.Write("task 1"));
  t1.Start();    

Możemy również poczekać aż task zostanie wykonany:

Task t1 = new Task(() => Console.Write("task 1"));
t1.Start();
t1.Wait();

Ciekawszym jednak rozwiązaniem jest tzw. kontynuacja zadań. Możemy stworzyć listę zadań, które będą wykonywały się po kolei. Często mamy przecież problemy współbieżne w których wykonanie kolejnego etapu musi być poprzedzone zakończeniem poprzedniego. Za pomocą ContinueWith możemy w łatwy sposób stworzyć taki plan wykonania:

Task t1 = new Task(delegate()
 {                    
     Console.Write("task 1");
     Thread.Sleep(5000);
 }
);
Task t2 = t1.ContinueWith((t) => Console.Write("task 2"));
t1.Start();
t2.Wait();

Powyższy kod gwarantuje, że zadanie t2 zostanie wykonane dopiero po zakończeniu t1.

Synchronizacja wątków(AutoResetEvent, ManualResetEvent, Interlocked), część 3

W celu synchronizacji wątków można wykorzystać mechanizm zdarzeń: ManualResetEvent oraz AutoResetEvent. Rozwiązanie polega na zastosowaniu sygnalizacji. Chcąc wejść do sekcji krytycznej piszemy:

ManualResetEvent resetEvent = new ManualResetEvent(false);
resetEvent.WaitOne();

W konstruktorze ustawiamy początkową wartość sygnału na false(brak sygnału). Następnie wywołujemy metodę WaitOne, która czeka na nadejście sygnału. Metoda blokuje kod aż  do momentu gdy w jakimś miejscu kodu zostanie wysłane zdarzenie za pomocą metody Set:

resetEvent.Set();

AutoResetEvent działa bardzo podobnie – jedyną różnicą jest fakt, że po otrzymaniu sygnału w WaitOne, stan zostaje ponownie ustawiony na false. Rozważmy dwa fragmenty kodu z ManualResetEvent oraz AutoResetEvent:

ManualResetEvent resetEvent = new ManualResetEvent(true);
resetEvent.WaitOne();
resetEvent.WaitOne();
MessageBox.Show("Hello world"); // zostanie wykonane

AutoResetEvent resetEvent = new AutoResetEvent(true);
resetEvent.WaitOne();            
resetEvent.WaitOne();
MessageBox.Show("Hello world"); // nigdy nie zostanie wykonane

W przypadku ManualResetEvent, WaitOne sprawdza tylko sygnał i nie modyfikuje go. Zatem wiadomość MessageBox zostanie wyświetlona. W przypadku AutoResetEvent, kod zostanie zablokowany na drugim wywołaniu WaitOne ponieważ  pierwsze wykonanie WaitOne ustawi sygnał na false.

Interlocked jest bardzo wydajnym narzędziem(statyczną klasą) umożliwiającym wykonywanie podstawowych operacji takich jak inkrementacja czy zamiana dwóch liczb w sposób atomowy czyli bezpieczny z punktu widzenia współbieżności. Konkretnie do dyspozycji mamy następujące metody:

Add

Dodanie dwóch liczb ze sobą.

CompareExchange

Porównanie ze sobą dwóch liczb i w przypadku gdy są one równe zastąpienie pierwszej z nich wartością dostarczoną w drugim parametrze.

Decrement

Zmniejszenie wartości o jeden.

Exchange

Zamiana wartości zmiennych.

Increment

Inkrementacja(zwiększenie o jeden).

Read

Przydatne na procesorach 32 bitowych gdzie operacje czytania zmiennych 64-bitowych nie jest atomowa.

Przykład użycia:

int value = 5;
Interlocked.Increment(ref value);
System.Diagnostics.Debug.Assert(value == 6);

W następnej, ostatniej części napiszę o kilku  nowościach w .NET 4.0, zapraszam.

Synchronizacja wątków(semafor, mutex), część 2.

W dzisiejszym poście przedstawię zasadę działania semafora oraz mutexa. Zacznijmy od teorii, czym jest semafor i jak można go zaimplementować? Otóż semafor jest sposobem na realizację wzajemnego wykluczania – zapewnienia, że tylko określona liczba wątków będzie mogła jednocześnie wykonać dany fragment kodu. Wyróżniamy semafory binarne, które dopuszczają maksymalnie jeden wątek oraz semafory ogólne, które umożliwiają jednoczesny dostęp określoną przez programistę liczbę wątków.

Implementacja semafora wymaga zaprogramowania dwóch metod: wait oraz signal. Programista wchodząc do sekcji krytycznej wywołuję wait a wychodząc wykonuje signal. Zatem kod źródłowy semafora ogólnego mógłby wyglądać następująco:

// ustawienie wartośći początkowej semafora
counter=5;

// metody atomowe
void Wait()
{
    while(counter<=0){}
    counter--;
}
void Signal()
{
    counter++;
}

Jeśli zasoby aktualnie są używane, metoda Wait będzie blokować dalsze wykonywanie kodu. Dopiero w momencie gdy sekcja krytyczna zostanie opuszczona(wywołanie Signal), warunek w Wait zostanie spełniony i dalszy kod będzie mógł się wykonać. Należy zaznaczyć, że metody muszą być atomowe ponieważ w przeciwnym razie mogą wystąpić problemy synchronizacyjne opisane w poprzednim poście. W przypadku gdy counter posiada wartość początkową równą 1, mamy do czynienia z semaforem binarnym.

Powyższy kod pokazałem tylko po to aby rozjaśnić sposób działania semafora. C# posiada bowiem  gotową klasę System.Threading.Semaphore:

System.Threading.Semaphore semaphore = new System.Threading.Semaphore(1, 1);
semaphore.WaitOne();
// sekcja krytyczna
semaphore.Release(1);

Konstruktor przyjmuje kolejno aktualną oraz maksymalną wartość licznika counter.

Przejdźmy teraz do następnego mechanizmu synchronizacji – Mutex’a. Mutex od strony użytkownika wygląda bardzo podobnie do semafora. Umożliwia jednak synchronizację na poziomie procesów a nie tylko wewnątrz AppDomain.

Należy również wspomnieć o zasadzie  “principle of ownership” – tylko wątek który nałożył blokadę może ją później zdjąć. W przeciwieństwie do semaforów, nie możemy ustawić blokady w wątku A a zdjąć jej w wątku B – zakończy się to wyrzuceniem wyjątku.

Klasycznym przykładem wykorzystania mutexów jest zapewnienie, że tylko jedna instancja programu zostanie uruchomiona:

class Program 
{ 
    static void Main(string[] args) 
    { 
        Mutex oneMutex = null; 

        const string MutexName = "SingleInstance";
        try 
        {         
            oneMutex = Mutex.OpenExisting(MutexName); 
        } 
        catch (WaitHandleCannotBeOpenedException) 
        { 
            // Mutex nie istnieje, obsługa wyjątku
        }         
        if (oneMutex == null)         
        {         
            oneMutex = new Mutex(true, MutexName); 
        } 
        else 
        {         
            oneMutex.Close(); 
            return; 
        } 
        // tworzenie okna itp.
    } 
}

Z przykładu widać, że podczas tworzenia obiektu Mutex można przekazać jego nazwę, która służyć będzie do rozpoznawania obiektu w różnych procesach. W przypadku gdy funkcja OpenExisting zwróci wartość różną od NULL(Mutex już utworzony), program zakończy działanie ponieważ zostanie wykonana instrukcja “return;”.

Oczywiście Mutex posiada również metody WaitOne oraz ReleaseMutex. Sposób użytkowania jest niemalże identyczny jak w przypadku semafora binarnego, więc nie będę pokazywał już kodu źródłowego.

W następnym poście planuje opisać klasy ManualResetEvent, AutoResetEvent oraz Interlocked – zapraszam do odwiedzenia bloga za kilka dni.

Synchronizacja wątków w C# (lock, Monitor), część 1

Najtrudniejszym zadaniem w programowaniu współbieżnym jest programowanie sekwencyjne a uściślając synchronizacja wątków;). Pewne operacje w naszych programach muszą być wykonywane w sposób sekwencyjny. Często dostęp do danych współdzielonych nie może odbywać się w sposób równoległy. Rozważmy klasyczny problem zwiększania liczby o jeden:

counter = counter + 1;

Jeśli zmienna counter jest współdzielona przez kilka wątków, powyższa  operacja jest niepoprawna. Dlaczego? Zacznijmy od początku. Zwiększanie liczby o jeden, tak naprawdę składa się z trzech operacji:

  1. Wczytanie zmiennej do rejestru procesora.
  2. Zwiększenie wartości w rejestrze o jeden.
  3. Przekopiowanie wartości rejestru z powrotem do zmiennej.

Załóżmy, że counter ma wartość 10. Wątek T1 chce zwiększyć wartość i wczytuje ją do rejestru. Następnie wątek T2 również chce zwiększyć wartość i kopiuje ją do rejestru(wciąż counter==10), zwiększa o jeden i przenosi wynik z powrotem do zmiennej. Zmienna zatem w tej chwili ma już wartość 11. Wątek T1 jednak w swoim rejestrze ma nieaktualną wartość 10. Bez synchronizacji, wątek T1 zwiększy wartość rejestru o 1 i otrzyma nieprawidłowy wynik 11(powinno być 12). Jeśli zamotałem, to mam nadzieję, że poniższy pseudokod rozjaśni problem:

;Wątek T1
Time1:    Kopiowanie wartości do rejestru.
Time4:    Zwiększenie rejestru o jeden. ;rejestr w tej chwili ma już nieaktualną wartość.
Time5:    Przekopiowanie rejestru do zmiennej counter.

;Wątek T2
Time1:    Kopiowanie wartości do rejestru.
Time2:    Zwiększenie rejestru o jeden.
Time3:    Przekopiowanie rejestru do zmiennej counter.

W programowaniu równoległym nie można zakładać, że wątki pracują w tym samym tempem. Każda operacja może trwać dłużej lub krócej w zależności od rdzenia procesora, priorytetu itp.

Zatem wszelkie operacje podzielne powinny być wykonywane w specjalnych obszarach kodu do których dostęp ma tylko jeden wątek jednocześnie –  tzw. sekcjach krytycznych.

C# posiada wiele mechanizmów synchronizacji. Zacznijmy od najpopularniejszego – operatora lock:

class Example
{
    private object m_SyncObject=new object();
    public void ThreadMethod()
    {    
        lock(m_SyncObject)
        {
            // sekcja krytyczna
        }
    }
}

Jak widać pewnie  ułatwienie dla synchronizacji posiada już sam język C# dostarczając słowo kluczowe lock. Niezbędnym jest dostarczenie obiektu, który stanowi pewne odniesienie dla synchronizacji. Za pomocą operatora lock blokujemy dostęp do dostarczonego obiektu (w tym przypadku m_SyncObject). Jeśli drugi wątek będzie chciał w tym samym momencie wejść do obszaru lock, będzie musiał poczekać aż pierwszy skończy wszelkie operacje zawarte w klauzuli lock. Wejście do obszaru lock z tym samym obiektem synchronizującym (m_SyncObject) możliwe jest wyłącznie przez jeden wątek .

Warto zaznaczyć, że nie powinno się używać referencji this oraz operatora lock:

class Example
{    
    virtual public void ThreadMethod()
    {    
        lock(this)
        {
            // sekcja krytyczna
        }
    }
}

Powyższy kod wywoła blokadę (deadlock)  w sytuacji gdy użytkownik klasy napiszę np.:

Example example=new Example();
lock(example)
{
    example.ThreadMethod();
}

Pierwszy lock(example) zadziała, jednak następny w ThreadMethod już nie zostanie nadany ponieważ referencje this oraz example odnoszą się do tego samego obiektu i tym samym lock(this) będzie czekał aż lock(example) zostanie zwolniony(co nie nastąpi nigdy).

Projektując biblioteki nie możemy przewidzieć w jaki sposób przyszli użytkownicy będą korzystać z naszej klasy więc powinniśmy pisać w sposób jak najbardziej elastyczny.

Następnym mechanizmem jest klasa Monitor. W rzeczywistości jest ona tym samym co lock. Słowo kluczowe lock  zostało wprowadzone po to aby programiści nie musieli pisać poniższego kodu:

class Example
{
   private object m_SyncObject = new object();
   public void ThreadMethod()
   {
       System.Threading.Monitor.Enter(m_SyncObject);
       try
       {
           // sekcja krytyczna
       }
       finally
       {
           System.Threading.Monitor.Exit(m_SyncObject);
       }
   }
}

Zwróćcie uwagę na try i finally. Bez tego awaria sekcji krytycznej(wyjątek) mogłaby spodować błędne zakończenie(brak wywołania metody Exit). Polecam wszystkim jednak używanie lock ponieważ trudniej w nim o popełnienie błędu takiego jak np. brak wywołania Exit. Warto jednak przyjrzeć się metodzie Monitor.TryEnter, która pozwala na sprawdzenie czy wejście do sekcji krytycznej jest możliwe.

W następnych postach planuje opisanie klas Mutex, Semaphore, AutoResetEvent, ManualResetEvent, Interlocked oraz o kilku nowościach wprowadzonych w .NET 4.0.

RIA Services i eager loading.

Domyślnie kolekcje i referencje do innych encji używają opóźnionego ładowania (lazy loading). Wszystkie encje podlegające temu mechanizmowi są ładowane w tle(w sposób niewidoczny dla programisty) w momencie kiedy wymagany jest do nich dostęp. Załóżmy, że mamy encję Invoice, która zawiera kolekcję produktów:

class Invoice
{
    EntityCollection<Product> Products{get;set;}
    public string ReceiverName{get;set;}
    public string IssuerName{get;set;}
}

Gdy użyjemy zapytania odczytującego fakturę, zostaną zwrócone wyłącznie właściwości ReceiverName oraz IssuerName – bez kolekcji Products.

W większości przypadków mechanizm opóźnionego ładowania sprawdza się, jednak czasami chcemy mieć od razu zarówno wszystkie właściwości jak i kolekcje danych – bez potrzeby wysyłania dodatkowego zapytania o kolekcje w drugim zapytaniu. W takim przypadku musimy skorzystać z eager loading(ładowanie zachłanne).

Użycie eager loading w RIA Services składa się z dwóch etapów: skonfigurowania metadanych oraz wywołania metody include przy odczytywaniu danych.

Metadane konfigurujemy za pomocą klas częściowych w bibliotece w której posiadamy model encji(ADO .NET Entity Data Model):

[MetadataType(typeof(Invoice.InvoiceMetadata))]
public partial class Invoice
{
   internal sealed class InvoiceMetadata
   {
       [Include]
       public EntityCollection<Product> Products;
   }

}

Następnie dopisujemy metodę w DomainService:

public IQueryable<Invoice> GetInvoiceWithProducts()
{
  return this.ObjectContext.InvoiceSet.Include("Products");
}

Zwróćcie uwagę na metodę Include – to ona jest odpowiedzialna za eager loading. Teraz po stronie klienta możemy załadować wszystkie faktury wraz z produktami za pomocą poniższego kodu:

m_DataContext.Load<Entities.Invoice>(m_DataContext.GetInvoiceWithProducts());

Azure i RIA Services

Azure to potężna platforma umożliwiająca przetwarzanie w chmurze. Po zainstalowaniu SDK szybko okaże się jednak , że brakuje tam szablonów wspomagających tworzenie aplikacji opartych o RIA Services. W poście przedstawię więc krok po kroku jak można stworzyć projekt RIA Services współpracujący z Azure.

Zaczynamy od stworzenia podstawowego projektu “Windows Cloud Service”.

image

Jako WebRole wybieramy ASP .NET.

image

Po utworzeniu, okno “Solution Explorer” powinno wyglądać mniej więcej tak:

image

Następnie dodajemy projekt aplikacji Silverlight.

image

Przy tworzeniu należy pamiętać aby zaznaczyć opcje hostowania Silverlight na wcześniej utworzonym Web Role. Ponadto należy również zaznaczyć opcję “Enable .NET RIA Services”.

image

W tej chwili mamy już wszystkie projekty utworzone. Następnym zadaniem jest dodanie referencji do niezbędnych bibliotek DLL oraz modyfikacja plików konfiguracyjnych.

W projekcie  WebRole dodajemy  referencje do 3 bibliotek: System.ComponentModel.DataAnnotations, System.Web.DomainServices oraz System.Web.Ria.

image

Następnie w pliku Web.config dopisujemy następujący HTTP handler:

<add name="DataService" verb="GET,POST" path="DataService.axd" type="System.Web.Ria.DataServiceFactory, System.Web.Ria, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

Kolejnym krokiem jest modyfikacja projektu Azure. Otwieramy plik ServiceDefinition.csdef a następnie ustawiamy atrybut enableNativeCodeExecution na true dla elementu WebRole:

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="CloudService1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebRole1" enableNativeCodeExecution="true">
    <InputEndpoints>
      <InputEndpoint name="HttpIn" protocol="http" port="80" />
    </InputEndpoints>
    <ConfigurationSettings>
      <Setting name="DiagnosticsConnectionString" />
    </ConfigurationSettings>
  </WebRole>
</ServiceDefinition>

W tej chwili mamy już gotowy projekt RIA Service wykorzystujący platformę Azure. Możemy teraz stworzyć sobie ADO .NET Entity Data Model a następnie Domain Service w celu przetestowania stworzonego projektu. Krótki przegląd możliwości RIA Services znajdziecie tutaj.

Wydajność wątków w C#

W języku C# mamy kilka mechanizmów tworzenia wątków. Różnią się one zarówno wydajnością jak i przeznaczeniem.

Zacznijmy więc od najpopularniejszego sposobu a mianowicie klasy System.Threading.Thread. Stworzenie wątku polega na inicjalizacji klasy oraz wywołania metody Start:

public class ThreadExample
{
    public CreateThread()
    {            
        System.Threading.Thread thread = new System.Threading.Thread(ThreadMethod);
        thread.Start(null);
    }
    private void ThreadMethod(object parameters)
    {
        while(true)
        {
            // jakiś kod
        }
    }
}

ThreadMethod zawiera kod, który chcemy wykonać współbieżnie. Warto także wspomnieć o właściwości Thread.IsBackground. Gdy ustawiamy ją na true(domyślnie jest false), wtedy wątek stanie się tzw. “background thread” – wątkiem zależnym od rodzica. W sytuacji gdy użytkownik zamknie naszą aplikację, automatycznie zostaną zamknięte wszystkie wątki “background”. Gdybyśmy nie ustawili IsBackground na true, po zamknięciu aplikacji, stworzone przez nas wątki nadal by pracowały, uniemożliwiając tym samym prawidłowe zamknięcie programu.

Najważniejszą jednak rzeczą, którą chciałem poruszyć w poście jest wydajność. Tworzenie klas Thread jest bardzo czasochłonne. O ile nie piszemy programu z naprawdę dużą ilością wątków, to podejście polegające na użyciu klasy Thread jest jednym z najgorszych rozwiązań. Związane jest to z długim czasem wymaganym na inicjalizacje tej klasy. Użycie Thread jest dobre gdy tworzymy pule wątków, które działają przez cały czas działania aplikacji.

Gdy tworzymy często nowe wątki, znacznie lepszym rozwiązaniem jest skorzystanie z gotowej puli wątków, które czekają już zainicjowane na użycie:

public class ThreadPoolExample
{
    public ThreadPoolExample()
    {        
        System.Threading.ThreadPool.QueueUserWorkItem(ThreadPoolMethod);
    }
    private void ThreadPoolMethod(object state) 
    {
        while(true)
        {
            // jakiś kod
        }
    }
}

Wywołując metodę QueueUserWorkItem system sprawdza czy w puli wątków jest wolny wątek .Jeśli tak, zwraca nam go i przechodzi do wykonywania kodu współbieżnego. Wątki zatem nie są tworzone za każdym razem od nowa – zawsze zwracane nam są już gotowe wątki do użycia. Gdy skończymy wykonywanie kodu współbieżnego, wątek nie zostaje zniszczony a wyłącznie zwrócony z powrotem do puli.

Dodatkowo warto wspomnieć, że wszystkie wątki z puli są typu “background”.

Gdy tworzymy interfejs użytkownika i chcemy jakaś operację wykonać w tle(np. połączenie się z WCF) warto zastanowić się nad klasą  BackgroundWorker. Przede wszystkim jest ona bardzo łatwo w użyciu(można ją nawet przeciągnąć z toolbox’a jako zwykłą kontrolkę) oraz posiada kilka zdarzeń, które są istotne dla GUI – np. powiadomienie o postępie prac. Krótki przykład:

public partial class Form1 : Form
{
   public Form1()
   {
       InitializeComponent();
       BackgroundWorker backgroundWorker = new BackgroundWorker();
       backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
       backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
       backgroundWorker.RunWorkerAsync();
   }

   void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
   {
       // e.ProgressPercentage
   }

   void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
   {
       while (true)
       {
           // jakiś kod kod
       }
   }        
}

Jeśli chcemy wykonywać pewną operacje co jakiś czas np. aktualizację lokalnej bazy danych z danymi otrzymywanymi przez usługę sieciową warto użyć timer’a:

public partial class Form1 : Form
{
   public Form1()
   {
       InitializeComponent();
       System.Threading.Timer timer = new System.Threading.Timer(ThreadMethod,null,0,1000);
       
   }
   private void ThreadMethod(object state)
   {
        // jakiś kod
   }
}

Jak widać ten specjalny timer znajduje się w przestrzeni nazw System.Threading. Metoda ThreadMethod będzie wykonywana współbieżnie co określony czas, przekazany w ostatnim parametrze konstruktora.

Kolumny SPARSE w SQL Server

Definiując tabelę np. za pomocą T-SQL istnieje możliwość opatrzenia kolumny atrybutem SPARSE. Kolumny SPARSE są specjalnymi kolumnami zoptymalizowanymi na przechowanie wartości NULL. W sytuacji gdy dana kolumna często ma wartość NULL(a dokładniej pole w wierszu), warto zastanowić się nad użyciem tego parametru ponieważ oszczędności pamięciowe są dość znaczące. Z drugiej jednak strony, jeśli będziemy chcieli zapisać wartość, która jest różna od NULL w kolumnie typu SPARSE zużyjemy więcej pamięci. Oszacowane oszczędności, zależne od typu danych znajdziecie tutaj. Aby stworzyć więc tabelę z kolumną SPARSE wystarczy opatrzyć ją atrybutem:

CREATE TABLE Example
(
    ID int PRIMARY KEY,
    SparseColumnExample smallint SPARSE NULL
) ;

Ze względu na charakterystykę kolumn SPARSE kolumna oczywiście musi być również typu NULLABLE. Podobnie kolumna taka nie może posiadać wartości domyślnej co jest uzasadnione(po co optymalizacja NULL skoro zawsze będzie wstawiona wartość domyślna). Dodatkowo niektóre typy danych nie mogą być opatrzone SPARSE a dokładniej: geography, text, geometry, timestamp, image, ntext  oraz wszelkie typy zdefiniowane przez użytkownika.

Wartość NULL oraz typy nie będące referencjami

W programach często wykorzystujemy wartość NULL do określenia np. czy operacja została prawidłowo wykonana. Niestety w przypadku typów “VALUE” nie można wykorzystać bezpośrednio wartości NULL ponieważ przetrzymują one wartość a nie adres komórki w pamięci.

Jeśli jednak mimo wszystko chcemy wykorzystać do porównania NULL, istnieje taka możliwość poprzez strukturę Nullable:

Nullable<int> value = 5;
value = null;
System.Diagnostics.Debug.Assert(value == null);

Ponadto c# dostarcza pewien skrót składniowy i zamiast pisać Nullable<int>wystarczy int?:

  int? value = 5;
  value = null;
  System.Diagnostics.Debug.Assert(value == null);