czwartek, 28 lutego 2013

[C#|Visual Studio] C#: Konstrukcja dynamic i ExpandoObject

W języku C# od wersji 4.0 mamy do dyspozycji konstrukcję składniową zaczynającą się od słowa dynamic. Słowo to zapewnia dynamiczną typowalność, to znaczy, że typ takiej zmiennej zostanie ustalony nie na poziomie kompilacji (jak w przypadku słowa var), lecz w czasie pracy programu, stąd owa dynamiczność. Poniżej prosty przykład:

dynamic unknownVariable;
unknownVariable = "this is message";
Console.WriteLine(unknownVariable);
unknownVariable = DateTime.Now;
Console.WriteLine(unknownVariable);

Jeżeli zdarzy nam się, że na przykład do daty z powyższego kodu będziemy chcieli dodać liczbę, co nie jest dozwolonym działaniem, nie otrzymamy błędu kompilacji, ale wyjątek typu RuntimeBinderException, o którym dokumentacja MSDN mówi, że:
Represents an error that occurs when a dynamic bind in the C# runtime binder is processed.
Kolejnym ciekawym typem wykorzystywanym do dynamicznego typowania jest ExpandoObject. Obiekty takie pozwalają na dynamiczne typowanie pól obiektu, co znane jest z innych języków programowania, takich jak np. JavaScript.

dynamic expando = new ExpandoObject();
expando.val1 = "Another silly message";
expando.count = 911;
expando.Pi = 3.14;
foreach (var prop in expando)
{
    Console.WriteLine(prop.GetType());
    Console.WriteLine(prop);
}

Powyższy kod wypisze na konsoli następujące rzeczy:


Zatem widzimy, że ExpandoObject jest odpowiednim słownikiem, umożliwiającym składowanie obiektów dowolnego typu, ze względu na to, że w C# wszystko pochodzi od klasy object.
Aby pobrać pojedynczą wartość, wystarczy zrzutować obiekt dynamicznie typowany do słownika.

var singleValue = ((IDictionary<string, object>) expando)["val1"];

wtorek, 26 lutego 2013

[WPF] ExpressionBlend: SampleData

Tworząc w WPF interfejs użytkownika, często potrzebujemy przykładowych kolekcji danych, np. wyświetlanych w ListBoxie. Korzystając z Blenda, można w prosty sposób wygenerować kolekcje przykładowych danych, składających się z propercji różnych typów.Rozpocząć należy od zakładki Data, widocznej na przykład w prawym górnym rogu okna designera.


W drugim kroku należy dodać kolekcję i zbudować odpowiedni model danych, a więc dodać properties interesującego nas typu.






Jedną z fajniejszych opcji jest możliwość dodawania Stringów różnego typu, w zależności od potrzeb aplikacji.


Dane należy następnie podłączyć jako ItemSource...


a w ListBoxTemplate  zbindować odpowiednie kontrolki od odpowiednich propercji, wybierając z menu dostępnego pod prawym przyciskiem myszy opcję Data bind Content to Data..

Jeżeli to nie wystarcza, w każdej chwili można utworzyć przykładowe dane w oparciu o własny plik XML z danymi.


Po dodaniu przykładowych danych warto także spojrzeć do folderu SampleData, tworzonego przez Blenda. Tworzona jest automatycznie klasa wspierająca INotifyPropertyChanged, dzięki czemu wszystkie zmiany modelu danych będą odświeżały UI i na odwrót.

niedziela, 24 lutego 2013

[HTML|JS|CSS] HTML5: IndexedDb

Ciekawym trendem, jaki można zaobserwować w standardzie HTML5 jest możliwość składowania coraz większej ilości danych po stronie klienta. Przestarzałe ciasteczka zastępowane są przez znacznie lepszy mechanizm localStorage. To jednak nie wszystko. Pojawia się także możliwość składowania danych w dokumentowej bazie IndexedDb. Podobnie jak w WebStorage, dane zapisywane są dla danej strony internetowej i tylko z jej poziomu mogą być odczytane. Tym razem możemy jednak składować całe obiekty JavaScriptowe, a nie tylko ciągi znaków. Co więcej, zgodnie z dokumentacją IndexedDb, pojemność takiej bazy praktycznie ograniczona jest jedynie miejscem na dysku. Same zapytania wykonywane są w sposób asynchroniczny, programista zatem musi ich wynik obsłużyć w callbackach.

Pracę z indexedDb, technologią mocno eksperymentalną, nad którą prace cały czas trwają, warto rozpocząć od  przypisania sobie dla wygody każdej z możliwych implementacji bazy do jednej zmiennej.

window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;

Jak widać ostatnim silnikiem przeglądarek, który nie wspiera indexedDb jest Presto, na którym tworzona była Opera (obecnie opera ma także być tworzona w oparciu o webkit, więc problem znika dla nowszych wersji).

Zapisywanie obiektów do bazy odbywa się poprzez odpowiednie callbacki.

$("#store").click(function(){
 var value = $("#msg").val();
 var request = indexedDB.open("MyDb", 1); //1
 request.onsuccess = function (evt) {
  db = request.result;
  var transaction = db.transaction("messages", "readwrite"); //2
  var objectStore = transaction.objectStore("messages"); //3                   
  var req = objectStore.add({ date: new Date().toLocaleString(), message: value });
  req.onsuccess = function (evt) {
   console.log('successfully saved');
  };
 };
 request.onerror = function (evt) {
  console.dir(evt);
 };

 request.onupgradeneeded = function (evt) {
  //...
 };
});

Ważne są trzy linie:
  1. Otwieramy bazę danych o nazwie MyDb, opcjonalnie jako drugi parametr podajemy wersję schematu bazy, do której chcemy się łączyć
  2. Pierwszy argument może być także tablicą i jest listą kontenerów, na których będzie wykonywana transakcja
  3. Żeby dodać obiekt do pojedynczego kontenera potrzebujemy obiekt typu objectStore, o nazwie messages
Powyższy kod sprawdza się pod warunkiem, że mamy już utworzoną bazę i kontener, w przeciwnym wypadku wywołana zostanie funkcja onupgradeneeded. 

request.onupgradeneeded = function (evt) {
 var objectStore = evt.currentTarget.result.createObjectStore("messages", 
                             { keyPath: "id", autoIncrement: true 
                         });

    objectStore.createIndex("date", "date", { unique: false });
    objectStore.createIndex("message", "message", { unique: false });
};

Tworzymy objectStore o nazwie messages, posiadający unikalny autoinkrementujący się identyfikator. Dodatkowo, na potrzeby wyszukiwania, można utworzyć dwa indeksy.

Odczyt danych

Podczas odczytu wykorzystuje się indeksy, zwracające wartość i wskaźnik na następny element z kolekcji.

$("#restore").click(function(){
 var request = indexedDB.open("MyDb");
 request.onsuccess = function(evt){
  var db = request.result;
  var transaction = db.transaction("messages");
  var objectStore = transaction.objectStore("messages");
  var req = objectStore.openCursor();
  req.onsuccess = function(evt){
   var cursor = evt.target.result;
   if(cursor){
    var obj = cursor.value;
    var li = $("<li>");
    li.html(obj.date + ", " + obj.message);
    items.append(li);
    cursor.continue();
   }
  }
 }
});

Usuwanie

Korzystając z unikalnych indeksów, usuwanie staje się bardzo proste.Na przykład dla elementu o indeksie 1


$("#removeFirst").click(function(){
 var request = indexedDB.open("MyDb");
 request.onsuccess = function (evt) {
  db = request.result;
  var req = db.transaction(["messages"], "readwrite")
            .objectStore("messages").delete(1);
        }
});

Na koniec warto wspomnieć o tym, jak wygodnie można oglądać stan bazy w przeglądarce Google Chrome, w zakładce Resources.

piątek, 22 lutego 2013

[HTML|JS|CSS] HTML5: WebSockets

Poprzedni post wprowadził do tematyki WebSocketów oraz pokazał, jak prosto zaimplementować je po stronie serwerowej. Tym razem czas na stronę kliencką, gdzie dzięki API HTML5 możemy w prosty sposób wysyłać i odbierać wiadomości. Kluczową funkcją konstruktora jest WebSocket, gdzie jako parametr podaje się URI serwera z gniazdem. Do dyspozycji mamy cztery zdarzenia, do których należy dopisać callbacki. Są to:

  • open
  • message
  • error
  • close
Do komunikacji z serwerem mamy funkcje send i close.

var close = document.getElementById("close");
var send = document.getElementById("send");

socket = new WebSocket("ws://localhost:9001", "echo-protocol");

socket.addEventListener("open", function(event) {
  status.textContent = "Connected";
});

socket.addEventListener("message", function(event) {
  var element = document.createElement('li');
  element.innerHTML = event.data;
  ul.appendChild(element); 
});

socket.addEventListener("error", function(event) {
  alert('Error');
});

socket.addEventListener("close", function(event) {
  status.textContent = "Not Connected";
});


close.addEventListener("click", function(event) {
  message.textContent = "";
  socket.close();
});

send.addEventListener("click", function(event) {
  socket.send(text);
  text.value = "";
});

[HTML|JS|CSS] Node.js: WebSockets

WebSockety to ciekawa technologia wykorzystywana w modelu klient - serwer. Zasada działania jest bardzo prosta. Pomiędzy przeglądarką a serwerem otwierany jest dwukierunkowy kanał komunikacji. Obie strony mogą wysyłać wiadomości bez konieczności odpytywania się nawzajem, a także przetwarzać otrzymane wiadomości na zasadzie obsługi zdarzeń. Zgodnie ze specyfikacją, adresy uri dla websocketów zaczynają się od ws lub wss (dla połączeń szyfrowanych).

Implementując WebSockety w Node.js, najwygodniej jest skorzystać z modułu ws. Obsługa połączenia a także wiadomości tradycyjnie wykonywana jest w callbackach, przy czym należy pamiętać że callback dla wiadomości zagnieżdża się w callbacku obsługi połączenia.

var ws = require('ws');
var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({port: 9002});

wss.on('connection', function(ws) {
 ws.on('message', function(message) {  
  var date = new Date();
  var str = date.toLocaleDateString() + ' ' +
   date.toLocaleTimeString();

  var reversed = "";
  for (var i = message.length - 1; i >= 0; i--) {
   reversed += message[i];
  }
   ws.send(reversed);
  });

 var date = new Date();
 var str = date.toLocaleDateString() + ' ' +
  date.toLocaleTimeString();
 ws.send('WebSocket connected at: ' + str);
});

wtorek, 19 lutego 2013

[C#|Visual Studio] Threading: Tasks

Task to obiekt reprezentujący pewnną jednostkę pracy (fragment kodu), którą chcemy zrównoleglić. Korzysta on z puli wątków, co poprawia wydajność w stosunku do wątków uruchamianych przez klasę Thread. Głównym zadaniem Tasków jest optymalne wykorzystanie wielordzeniowości komputera, przy niskich kosztach pamięciowych zrównoleglania (możemy dodawać do puli wątków setki zadań, które zostaną wykonywane w najszybszy możliwy sposób). Co więcej wprowadzają kilka zupełnie nowych, bardzo ciekawych funkcjonalności, takich jak na przykład możliwość uruchamiania child - Tasków z poziomu danego Tasku.

Operacje poprzez Taski można wykonywać na kilka sposobów:

static void RunTasks()
{
    //1 delegate
    Task.Factory.StartNew(() => Console.WriteLine("Hello from a task!"));
    //2 Task<T> - return value
    Task<string> task = Task.Factory.StartNew<string>(() =>
    {
        using (var wc = new System.Net.WebClient())
            return wc.DownloadString("http://www.agh.edu.pl");
    });
    //3 constructor
    var task2 = new Task(() => Console.Write("Hello"));
    //wait for result
    Console.WriteLine(task.Result);
    task2.Start();
    //4 state object
    var task3 = Task.Factory.StartNew (Greet, "Hello");
    task3.Wait();  // Wait for task to complete.   
}

static void Greet (object state) { Console.Write (state); }

Startowanie child-Tasków:

Task parent = Task.Factory.StartNew(() =>
{
    Console.WriteLine("I am a parent");

    Task.Factory.StartNew(() =>
    {
        Console.WriteLine("I am detached");
    });

    Task.Factory.StartNew(() =>
    {
        Console.WriteLine("I am a child");
    }, TaskCreationOptions.AttachedToParent);
});

Dzięki opcji AttachedToParent, aby parent mógł zakończyć swoje zadanie, będzie musiał poczekać na wykonania childa z tą właśnie opcją.

Synchronizację z Taskami można wykonać na dwa sposoby: wywołując metodę Wait() z opcjonalnym timeoutem, lub pobierając wartość property Result. W obu przypadkach program musi zaczekać na zakończenie działania Tasków.

Wyjątki:

Wyjątki w kodzie Tasków są propagowane do wątku wywołującego taski i opakowane w specjalną klasę AggregateException.

static void ExceptionTest()
{
    int x = 0;
    Task<int> calc = Task.Factory.StartNew(() => 7 / x);
    try
    {
        Console.WriteLine(calc.Result);
    }
    catch (AggregateException aex)
    {
        Console.Write(aex.InnerException.Message);  // Attempted to divide by zero
    }
}

Wyjątki z child-Tasków propagowane są do parent Tasków.

Continuation

Metoda ContinueWith z klasy Task pozwala na wykonywanie podanego delegata po zakończeniu działania zadania.

Task task1 = Task.Factory.StartNew(() => Console.Write("antecedant.."));
Task task2 = task1.ContinueWith(ant => Console.Write("..continuation"));

Synchronizować można się także z wieloma Taskami stosując ContinueWithAll bądź ContinueWithAny.
 

poniedziałek, 18 lutego 2013

[C#|Visual Studio] Threading: klasa Parallel

Kolejną ciekawą funkcjonalnością wprowadzoną w .NET 4.0 jest klasa Parallel. Udostępnia ona trzy metody umożliwiające zrównoleglenie wykonywania kodu.
  • Invoke - wykonuje równolegle tablicę delegatów
  • For - pętla for wykonywana równolegle
  • Foreach - pętla foreach wykonywana równolegle
Metody te zapewniają wysoką wydajność przy zrównoleglaniu obliczeń, ponieważ wykorzystują mechanizm Tasków, zatem np. gdy podamy sto delegatów, to zostaną one rozpartycjonowane na taką ilość tasków, by zapewnić maksymalną efektywność.Wykonywania każdej z tej funkcji zakończy się, gdy skończą działanie wszystkie taski.

static void InvokeTest()
{
    Parallel.Invoke(() => new WebClient().DownloadString("http://www.agh.edu.pl/"),
        () => new WebClient().DownloadString("http://www.eaiib.agh.edu.pl/"));
}

static void ForTest()
{
    var keyPairs = new string[6];
    Parallel.For(0, keyPairs.Length,
                  i => keyPairs[i] = RSA.Create().ToXmlString(true));
    foreach (var keyPair in keyPairs)
        Console.Write(keyPair);
}

static void ForeachTest()
{
    var arr = new int[]
                  {
                      166, 147, 159, 156
                  };
    Parallel.ForEach(arr, 
        s => Console.WriteLine("Alfa Romeo {0}", s));
}

Metoda For jest przeładowana na wiele sposobów. Jeżeli potrzebujemy, by wykonywać skomplikowane operacje matematyczne, możemy skorzystać z wersji metody, w której możemy wykorzystać lokalną zmienną, inicjalizowaną przez jednego z delegatów.

static void ForLockerTest()
{
    object locker = new object();
    double grandTotal = 0;

    Parallel.For(1, 10000000,
      () => 0.0,
      (i, state, localTotal) =>
         localTotal + Math.Sqrt(i),
      localTotal =>
      { lock (locker) grandTotal += localTotal; }
    );

    Console.WriteLine(grandTotal);
}

niedziela, 17 lutego 2013

[C#|Visual Studio] Threading: PLINQ

.NET Framework w wersji 4.0 udostępnił programistom nową bibliotekę - PFX (Parallel Framework). Wprowadza ona nową jakość w programowaniu wielowątkowym. Jednym z najważniejszych osiągnięć PFX jest PLINQ - mechanizm oferujący automatyzację wszystkich kroków w zrównoleglaniu obliczeń. Kroki te to:
  • podział zadań na Taski 
  • wykonywanie Tasków na wątkach
  • zebranie wszystkich wyników w całość
Parallel LINQ to zatem nic innego, jak równoległa implementacja dobrze znanego LINQ to objects. Kluczem do użycia PLINQ jest specjalna metoda AsParallel(). To dzięki niej, w sposób deklaratywny programista wprowadza zrównoleglenie obliczeń. Prosty przykład to poszukiwanie liczb pierwszych, gdzie podczas filtrowania wykonuje się proste operacje matematyczne.

static void PrimeNumbers()
{
    IEnumerable<int> numbers = Enumerable.Range(3, 100000 - 3);

    var parallelQuery =
      from n in numbers.AsParallel()
      where Enumerable.Range(2, (int)Math.Sqrt(n)).All(i => n % i > 0)
      select n;

    int[] primes = parallelQuery.ToArray();
    foreach (var prime in primes)
    {
        Console.Write("{0} ", prime);
    }
}

Zapytania PLINQ, podobnie jak każde inne LINQ są wykonywane z opóźnieniem, w momencie, gdy np. wywołamy ToArray, pętlę foreach itd. Równoległość obliczeń powoduje jednak, że wyniki będą nieuporządkowane. Jeżeli zależy nam na tym, by wynik wyglądał tak, jak w sekwencyjnym zapytaniu, możemy dodatkowo wywołać metodę AsOrdered()

from n in numbers.AsParallel().AsOrdered()

Wywołania takie wpływa oczywiście na pogorszenie wydajności, dlatego takie zachowanie nie pojawia się domyślnie.

PLINQ optymalizuje zrównoleglenie pod kątem możliwości sprzętowych. To jedna z największych zalet zwłaszcza pod kątem przyszłości, gdzie liczba rdzeni w komputerach PC będzie ciągle wzrastać. W przypadku niektórych komputerów, PLINQ może wykonać obliczenia sekwencyjnie pomimo użycia AsParallel(), np. dlatego, że zrównoleglenie może pogorszyć wydajność. Programista ma możliwość wymuszenia równoległości instrukcją

from n in numbers.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism)

Kolejną ciekawą instrukcją jest WithDegreeOfParallelism(), gdzie podajemy ilość Tasków, na których ma być przetwarzane zapytanie. Szczególnie przydatne jest to w przypadku odpytywania WebSerwisów, gdzie tracimy czas nie na obliczeniach na CPU, ale na oczekiwaniu na odpowiedź.

from n in numbers.AsParallel().WithDegreeOfParallelism(6)

piątek, 15 lutego 2013

[C#|Visual Studio] Threading: Timers

Timery dostępny w świecie .NET można podzielić na takie, które działają w obrębie jednego wątku oraz na timery wielowątkowe. Wybór zależy oczywiście głównie od potrzeb, ale także, w przypadku zegarków jednowątkowych od technologii. Mamy więc:
  • System.Windows.Forms.Timer w Windows Forms
  • System.Windows.Threading.DispatcherTimer w WPF

Multithreaded Timers

Najprostszym timerem do pracy z wieloma wątkami jest System.Threading.Timer, który składa się jedynie z konstruktora i dwóch metod.

static void ThreadingTimer()
{
    Timer tmr = new Timer (Tick, "tick...", 5000, 1000);
    Console.ReadLine();
    tmr.Dispose();
}

static void Tick (object data)
{
    Console.WriteLine (data);
}

W konstruktorze przekazujemy callback cyklicznie wywoływany, parametr tego callbacka, następnie interwał po którym pojawi się pierwsze wywołanie i interwał pomiędzy kolejnymi wywołaniami.

Bardziej rozbudowaną wersją jest Timer umiejscowiony w namespace System.Timers.  Rozszerzono API o kilka opcji zmieniając konstruktor na bezparametrowy. Ustawienia podajemy przez properties,  a ponadto możemy go wyłączać przez property Enabled, a także zatrzymywać odliczanie używając metody Stop. Sekwencja Start - Stop - Start resetuje odliczony czas.

static void TimersTimer()
{
    var tmr = new System.Timers.Timer();
    tmr.Interval = 5000;
    tmr.Elapsed += tmr_Elapsed;
    tmr.Start();
    Console.ReadLine();
    tmr.Stop(); 
    Console.ReadLine();
    tmr.Start();
    Console.ReadLine();
    tmr.Dispose();
}

static void tmr_Elapsed(object sender, EventArgs e)
{
    Console.WriteLine("Tick");
}

Oba powyższe timery wykorzystują pulę wątków, co umożliwia uruchomienie wielu ich instancji z różnych wątków. Interwał pomiędzy kolejnymi wykonaniami callbacka nie zależy od czasu wykonywania samego callbacka, a dokładność w obliczaniu czasu szacuje się na 10-20 ms.

Timery używane w WinForm i WPF dostarczają podobne API, jednak warto pamiętać, że są dedykowane do pracy w swoich środowiskach. Timery te nie wykorzystują puli wątków, natomiast w znacznej mierze zależą od mechanizmów UI, tzw. "message pumping". Zatem zdarzenie tick jest dostępne w tym samym wątku, z którego wystartowano zegarek.

środa, 13 lutego 2013

[C#|Visual Studio] Threading: BackgroundWorker

Aplikacje pisane w takich technologiach jak WinForms czy WPF wymagają, aby interfejs użytkownika był responsywny niemal przez cały czas. Zatem wszystkie kosztowne czasowo operacje powinny być wykonywane asynchronicznie. Klasą, która umożliwia łatwe przeprowadzanie takich operacji jest BackgroundWorker. Klasa ta dostarcza kilka gotowych mechanizmów, takich jak:
  • łatwa możliwość przerwania pracy
  • możliwość bezpiecznej aktualizacji kontrolek WPF i Formsowych
  • przekazywanie wyjątków do wątku nadrzędnego
  • możliwość raportowania o stanie pracy workera
Poniższy przykład obrazuje wszystkie możliwości BackgroundWorkera:

static BackgroundWorker _bw;

public static void Run()
{
    _bw = new BackgroundWorker
    {
        WorkerReportsProgress = true,
        WorkerSupportsCancellation = true
    };
    _bw.DoWork += bw_DoWork;
    _bw.ProgressChanged += bw_ProgressChanged;
    _bw.RunWorkerCompleted += bw_RunWorkerCompleted;
    _bw.RunWorkerAsync ("Hello to worker");
    Console.WriteLine ("Press Enter in the next 5 seconds to cancel");
    Console.ReadLine();
    if (_bw.IsBusy) _bw.CancelAsync();
    Console.ReadLine();
}

static void bw_DoWork (object sender, DoWorkEventArgs e)
{
    for (int i = 0; i <= 100; i += 20)
    {
        if (_bw.CancellationPending) { e.Cancel = true; return; }
        _bw.ReportProgress (i);
        Thread.Sleep (1000);
    }
    e.Result = 123;
}

static void bw_RunWorkerCompleted (object sender,
                                    RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
        Console.WriteLine ("You canceled!");
    else if (e.Error != null)
        Console.WriteLine ("Worker exception: " + e.Error.ToString());
    else
        Console.WriteLine ("Complete: " + e.Result);      // from DoWork
}

static void bw_ProgressChanged (object sender,
                                ProgressChangedEventArgs e)
{
    Console.WriteLine ("Reached " + e.ProgressPercentage + "%");
}

Ustawienie odpowiednich propercji i zapięcie callbacków na zdarzenia jest konieczne w przypadku, gdy chcemy mieć możliwość anulowania i raportowania stanu pracy workera. Obie możliwości są opcjonalne. Dane pomiędzy wątkami przekazywane są poprzez odpowiednie EventArgs.

wtorek, 12 lutego 2013

[C#|Visual Studio] Threading: Event Wait Handles

Event Wait Handles wykorzystywane są do sygnalizacji pomiędzy wątkami, a więc w sytuacji gdy jeden wątek oczekuje na sygnał od innego. Mamy do dyspozycji kilka typów w C# 4.0

AutoResetEvent

Pozwala na powiadomienie jednego oczekującego wątku. Wątek czeka na sygnał. Oczekiwanie rozpoczyna wywołanie metody WaitOne, natomiast wątek notyfikowany jest za pomocą metody Set.

static EventWaitHandle _waitHandle = new AutoResetEvent(false);

static void RunAutoResetEvent()
{
    new Thread(Waiter).Start();
    Thread.Sleep(1000);
    _waitHandle.Set();
}

static void Waiter()
{
    Console.WriteLine("Waiting...");
    _waitHandle.WaitOne();
    Console.WriteLine("Notified");
}

Jeżeli metoda Set zostanie wywołana podczas gdy żaden wątek nie oczekuje, dopuszczony do działania zostanie pierwszy wątek, który wywoła metodę WaitOne. Metoda ta przyjmuje opcjonalnie jako parametr wartość timeoutu.

ManualResetEvent

Działa podobnie jak AutoResetEvent, z tą różnicą, że ustawienie Set powoduje, iż przepuszczony zostaje każdy wątek wołający WaitOne, natomiast Reset powoduje, że wątki czekają na powiadomienie.

CountdownEvent

Pozwala zaczekać na więcej niż jeden wątek, liczbę wątków, które muszą powiadomić określa się w konstruktorze. Wątki wysyłają sygnały poprzez metodę Signal.

static CountdownEvent _countdown = new CountdownEvent(3);

static void RunCountDownEvent()
{
    new Thread(SaySomething).Start("I am thread 1");
    new Thread(SaySomething).Start("I am thread 2");
    new Thread(SaySomething).Start("I am thread 3");
    _countdown.Wait();
    Console.WriteLine("All threads have finished speaking!");
}

static void SaySomething(object thing)
{
    Thread.Sleep(1000);
    Console.WriteLine(thing);
    _countdown.Signal();
}

Wywołanie metody Reset spowoduje przywrócenie licznikowi wyjściowej postaci.

poniedziałek, 11 lutego 2013

[C#|Visual Studio] Threading: Synchronizacja

Synchronizacja wątków jest szczególnie ważna, gdy korzystają one z tych samych zasobów, jednak niezsynchronizowane wątki mogą także w wielu przypadkach doprowadzić do nieprzewidywalnego zachowania programu. Konstrukcje koordynujące wątki dzieli się na cztery kategorie:

Blokowanie

Wątek zablokowany, to taki, którego wykonywanie z jakiegoś powodu zostało wstrzymane, na przykład poprzez operację Sleep bądź Join względem innego wątku. Wątek w stanie zablokowania oddaje swoją część procesora do momentu wznowienia. Odblokować wątek można na cztery sposoby:
  • warunek wznowienia zostaje spełniony
  • przekroczony timeout (jeżeli został ustawiony)
  • przerwanie przez Thread.Interrupt
  • wykonanie instrukcji Thread.Abort
Podczas blokowania wątków można spotkać także tzw. spinning, technikę bardzo niefektywną dla CPU polegającą na ciągłym sprawdzaniu pewnego warunku. Nieco lepszym rozwiązaniem jest usypianie wątku na krótki czas. Technika ta jest efektywna jedynie dla krótkich blokowań, rzędu milisekund, gdyż wtedy nie zachodzi konieczność cennego przełączania kontekstu.

while (!proceed) Thread.Sleep (10);

Stan wątku jest dostępny w każdej chwili za pomocą property ThreadState. Możliwe stany wątków przedstawione są na diagramie poniżej.


Wątki zostają zablokowane na przykład, gdy użyjemy instrukcji lock. Instrukcja ta jest skrótem składniowym odpowiadającym instrukcjom Monitor.Enter oraz Monitor.Exit. Obiekt na którym wykonywana jest blokada musi dziedziczyć po typie referencyjnym.

static readonly object _locker = new object();
static int _x;

static void Increment() 
{ 
    lock (_locker) _x++;
}

static void Decrement()
{
    lock (_locker) _x--;
}

public static void Run()
{
    for (int i = 0; i < 5; i++)
    {
        Task.Factory.StartNew(Increment);
        Task.Factory.StartNew(Decrement);
    }
}

Lock jest instrukcją atomową, chyba że w jego wnętrzu zostanie rzucony wyjątek.Podobny efekt można uzyskać stosując Mutex. Konstrukcja ta mimo iż jest kilkadziesiąt razy wolniejsza, ma jedną ważną przewagę nad lockiem - można za jej pomocą wykonywać blokady pomiędzy procesami.

static void RunMutex()
{
    using (var mutex = new Mutex(false, "Sample Mutex String"))
    {
        if (!mutex.WaitOne(TimeSpan.FromSeconds(3), false))
        {
            Console.WriteLine("Another app instance is running. Bye!");
            return;
        }
        RunProgram();
    }
}

static void RunProgram()
{
    Console.WriteLine("Running. Press Enter to exit");
    Console.ReadLine();
}

Nazwa Mutex-a pozwoli mu blokować procesy w obrębie całego komputera. Jeżeli powyższy program zostanie uruchomiony w dwóch instancjach, to druga instancja poczeka 3 sekundy i zakończy swoją pracę, jeżeli kod pierwszej instancji nie wykona się (nie zostanie wciśnięty Enter).

Semafor

Semafor działa jak lock, z tą różnicą że jest w stanie obsłużyć więcej niż jeden wątek. Liczba wątków definiowana jest przez programistę, a każdy nadmiarowy wątek musi poczekać, aż któryś z poprzedników skończy swoje działanie.

static SemaphoreSlim _sem = new SemaphoreSlim(3);
public static void Run()
{
    for (int i = 1; i <= 5; i++) 
        new Thread(Enter).Start(i);
}

static void Enter(object id)
{
    Console.WriteLine(id + " wants to enter");
    _sem.Wait();
    Console.WriteLine(id + " is in!");
    Thread.Sleep(1000*(int) id);
    Console.WriteLine(id + " is leaving");
    _sem.Release();
}

SemaphoreSlim to klasa z .NET 4.0 zoptymalizowana pod kątem czasu wykonywania operacji Wait i Release.

sobota, 9 lutego 2013

[C#|Visual Studio] Threading: Thread Pool

Pula wątków rozwiązuje dwa dosyć poważne problemy związane ze współbieżnością w .NET:
  • każdy wątek domyślnie wykorzystuje około 1MB pamięci
  • utworzenie nowego wątku wraz z organizacją pamięci dla niego trwa setki milisekund
Dzięki puli wątków zyskujemy automatyczne współdzielenie i odzyskiwanie już pracujących wątków. Ponadto kontrolowana jest maksymalna ilość wątków typu "worker" jakie mogą pracować w danej chwili. Zbyt duże ilości aktywnych wątków mogą zaburzać pracę systemu operacyjnego, co staje się niewygodne dla użytkownika. Gdy zastosuje się Thread Pool, to w przypadku przekroczenia maksymalnej liczby wątków, nowe wątki są kolejkowane i uruchamiane dopiero w momencie, gdy któreś z aktywnych wątków skończą swoją pracę.

Przed rozpoczęciem pracy z pulą wątków należy pamiętać, że będą one zawsze wątkami typu "background".

Dostęp do puli wątków można uzyskać na kilka sposobów:

Pula wątków poprzez Task Parallel Library (TPL)

TPL dostarcza specjalną klasę Task, która dostępna jest pod dwoma postaciami: generyczną i niegeneryczną. Wątek z Thread Pool można wystartować w poniższy sposób:

public static void Run()
{
    Task.Factory.StartNew(NonGenericTask);
}

public static void NonGenericTask()
{
    Console.WriteLine("This is thread from thread pool");
}

lub w sposób generyczny, gdzie możemy zwracać wartość z wątku.

public static void Run()
{
    Task<string> downloader = Task.Factory.StartNew(
        () => GenericTask("http://www.wisla.krakow.pl"));
    Console.WriteLine("Some operations...");
    var result = downloader.Result;
    Console.WriteLine(result);
}

public static string GenericTask(string uri)
{
    Console.WriteLine("This is thread from thread pool");
    using (var wc = new System.Net.WebClient())
        return wc.DownloadString(uri);
}

Warto pamiętać, że w momencie, gdy odwołujemy się do property Result, wątek główny zostanie zawieszony do momentu wywołania wątku z puli.

TPL został wprowadzony w .NET 4.0. W starszych wersjach zachodzi konieczność korzystania z puli wątków w nieco inny sposób.

Pula wątków poprzez klasę ThreadPool

Tutaj mamy do dyspozycji klasę ThreadPool, gdzie wywołując wątek możemy przez parametr podać stan (jako typ object).

public static void Run()
{
    Console.WriteLine("Without TPL");
    ThreadPool.QueueUserWorkItem(QueueUserWorkItemFcn, 
        DateTime.Today.ToShortDateString());
}

public static void QueueUserWorkItemFcn(object dateTime)
{
    Console.WriteLine("This is thread from thread pool, at {0}", dateTime);
}

Jeżeli chcemy zwracać obiekty z wątku nie używając TPL, należy wykorzystać tzw. asynchronous delegates.

public static void Run()
{
    Console.WriteLine("Asynchronous delegate");
    Func<string, string> func = new Func<string, string>(AsynchronousDelegate);
    IAsyncResult value = func.BeginInvoke("this is some text", null, null);
    Console.WriteLine("Some operations...");
    var str = func.EndInvoke(value);
    Console.WriteLine(str);
}

public static string AsynchronousDelegate(string letters)
{
    Console.WriteLine("This is thread from thread pool");
    IEnumerable<char> rev = letters.Reverse();
    string result = "";
    foreach (var @char in rev)
    {
        result += @char;
    }
    return result;
}

Operacja EndInvoke czeka aż asynchroniczny delegat wykona swoje zadanie, zawieszając główny wątek, odbiera rezultat przetwarzania, a także "przerzuca" wyjątek z wątku w tle do wątku głównego.

Wywołując BeginInvoke można jako drugi parametr przekazać callback, który wykona się po zakończeniu pracy wątku. Callback jako parametr przyjmuje typ IAsyncResult.

public static void Run()
{
    var method = new Func<string, string>(AsynchronousDelegate);
    method.BeginInvoke("My text", Callback, method);
}

public static void Callback(IAsyncResult result)
{
    var target = (Func<string, string>)result.AsyncState;
    string res = target.EndInvoke(result);
    Console.WriteLine(res);
}

sobota, 2 lutego 2013

[C#|Visual Studio] Threading: Podstawy

W języku C#, niezależnie od tego, czy tworzymy aplikację konsolową, WinFormsową czy WPFową, domyślnie operacje zawsze będą się wykonywały w jednym wątku, tworzonym przez CLR. Wątkami w .NEcie zarządza specjalny mechanizm zwany thread scheduler. Na jednordzeniowej maszynie wielowątkowość realizowana jest poprzez podział czasu pracy procesora, natomiast na wielordzeniowej wątki uruchamiane są na wszystkich rdzeniach, jednak należy pamiętać, że system operacyjny czy też inne uruchomione programy również korzystają z CPU, tak więc podział czasu i przełączanie wątków w praktyce odbywa się zawsze. Czas przełączenia szacuje się na dziesiątku milisekund.

Najprostszym sposobem na zrównoleglenie wykonywanych operacji jest klasa Thread. Poniższy przykład pokazuje, jak przy jej użyciu na wieloprocesorowej maszynie wykonywać operacje naprzemiennie.

public static void Run()
{
    Thread t = new Thread(WriteY);          
    t.Start();
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(i);
        Console.Write("x");
    } 
        
}

static void WriteY()
{
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(i);
        Console.Write("y");
    }
}

Warto pamiętać, że dla każdego wątku tworzony jest osobny memory stack. Informacje pomiędzy wątkami mogą być przekazywane np. poprzez zmienne statyczne. Nieumiejętne ich wykorzystanie może jednak spowodować poważne problemy. Aby uniknąć np. sytuacji, w której dwa wątki zapisują w tym samym czasie dane do jednej zmiennej, należy użyć tzw. Exclusive Lock. Użycie słowa kluczowego lock powoduje, że tylko jeden wątek w danej chwili może korzystać z zasobu, drugi musi poczekać.

public class LockTest
{
    static bool done;
    static readonly object locker = new object();

    public static void Run()
    {
        new Thread(Go).Start();
        Go();
    }

    static void Go()
    {
        lock (locker)
        {
            if (!done) { Console.WriteLine("Done"); done = true; }
        }
    }
}

Blokowany wątek nie zużywa zasobów CPU.

Kolejną ważną operacją jest Join, służący do synchronizowania kolejności wykonywania operacji. Jeżeli z poziomu jednego wątku startujemy inny podrzędny, możemy zażądać aby dalsze wykonywanie kodu wstrzymać do zakończenia działania tego podrzędnego wątku.

public class JoinTest
{
    public static void Run()
    {
        Thread t = new Thread(Go);
        t.Start();
        for (int i = 0; i < 100; i++) Console.Write("x");
        t.Join();
        Console.WriteLine("Thread t has ended!");
    }

    static void Go()
    {
        for (int i = 0; i < 1000; i++) Console.Write("y");
    }
}

Wątki można nazywać, co przydaje się podczas debugowania w Visual Studio. Można im także nadawać priorytety. Nadanie priorytetu decyduje o tym, ile czasu procesor poświęci na dany wątek. Domyślny priorytet to Medium.

public class ThreadNamePriorityTest
{
    public static void Run()
    {
        Thread.CurrentThread.Name = "Main Thread";
        Thread worker = new Thread(Go);
        worker.Name = "Worker";
        worker.Priority = ThreadPriority.BelowNormal;
        worker.Start();
        Go();
    }

    static void Go()
    {
        Console.WriteLine("Hello from " + Thread.CurrentThread.Name);
    }
}