Pokazywanie postów oznaczonych etykietą task. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą task. Pokaż wszystkie posty

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.
 

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);
}

poniedziałek, 26 grudnia 2011

[Ada | EN] Circular buffer using protected object

Theme
How to implement circular buffer in Ada programming language with use of protected object.

Circular buffer is a simple data structure that contains an fixed size array and two data pointers (start of valid data and the end of valid data). Protected object provides mutual exclusion of two synchronous tasks (Producer and Consumer). Data inside buffer is being modified by calling (with random delay times) one of two entries (Add, Take). Each of them contains a when statement that ensures data cohesion. If one of entries is being called and a condition associated with apropriate when statement is not fullfilled, the task is being suspended until the condition is done.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
with Ada.Text_Io;
use Ada.Text_Io;
with Ada.Numerics.Discrete_Random;
 
 
procedure Cyclic_Buffer is
 
   type Buffer is array (Integer range <>) of Integer;
   Size : Integer:=5;
 
   subtype Wait is Integer range 0..5;
   package Draw is new Ada.Numerics.Discrete_Random(Wait);
   use Draw;
   G : Generator;
 
   protected Protected_Buffer is
      entry Add(I : in Integer);
      entry Take(I : out Integer);
   private 
      B : Buffer(1..Size);   
      Counter : Integer:=0;
      GetIndex : Integer:=1;
      PutIndex : Integer:=1;
         
 end Protected_Buffer;
   
   task Producer;
   task Consumer;
 
   protected body Protected_Buffer is
 
      entry Add(I : in Integer) when Counter < Size is
      begin
         B(PutIndex) := I;
         PutIndex := PutIndex mod Size + 1;
         Counter := Counter + 1;                   
         end Add;
 
      entry Take(I : out Integer) when Counter > 0 is
      begin
         I:=B(GetIndex);
         GetIndex := GetIndex mod Size + 1;  
         Counter := Counter - 1;      
         end Take;
   end Protected_Buffer;   
   
 
 
task body Consumer is
   Cons_Delay_Time : Integer;
begin
   loop
      Reset(G);
      Cons_Delay_Time:=Integer(Random(G));
      delay Duration(1.3*Float(Cons_Delay_Time));
      Protected_Buffer.Take(Cons_Delay_Time);
      Put_Line("Consumer : Taking "&Cons_Delay_Time'Img);
      delay 0.15;
    end loop;
end Consumer;
 
task body Producer is
   Prod_Delay_Time : Integer;
begin
   loop
      Reset(G);
      Prod_Delay_Time := Integer(Random(G));
      delay Duration(Prod_Delay_Time);
      Protected_Buffer.Add(Prod_Delay_Time);
      Put_Line("Producer : Adding "&Prod_Delay_Time'Img);
      delay 0.15;
   end loop;
end Producer;
 
begin
   null;
end Cyclic_Buffer;

Comments:
[11 - 14] Random number generator declaration. It draws consecutive delay times for both tasks.
[27 - 28] Tasks are being triggered
[55] Delay time depends on a drawn number
[56] Local variable associates now with a value from the buffer
[77] All the instructions are being evolved in tasks so null instruction is here sufficient

Compiler: Ideone.com

niedziela, 25 grudnia 2011

[Ada | PL] Bufor cykliczny na obiekcie chronionym

Temat:
Realizacja bufora cyklicznego w języku Ada z wykorzystaniem obiektu chronionego.

Bufor cykliczny to prosta struktura danych zrealizowana w przypadku tego programu na tablicy pięcioelementowej, gdzie dane dodawane są i pobierane zgodnie z indeksami wskaźników (w programie GetIndex i PutIndex). Obiekt chroniony zapewnia wzajemne wykluczanie dostępu do zasobów dwóch współbieżnie działających zadań (producenta i konsumenta). Modyfikowanie danych w tablicy dokonuje się za pomocą dwóch instrukcji entry wywoływanych z obu zadań w losowych chwilach czasu. Instrukcje te zapewniają nie tylko synchronizację zadań, ale także zawieszenie zadania, jeśli nie jest spełniony warunek zapisany instrukcją when. Zadanie zostaje odwieszone, gdy warunek ponownie zostanie spełniony.



1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
with Ada.Text_Io;
use Ada.Text_Io;
with Ada.Numerics.Discrete_Random;
 
 
procedure Cyclic_Buffer is
 
   type Buffer is array (Integer range <>) of Integer;
   Size : Integer:=5;
 
   subtype Wait is Integer range 0..5;
   package Draw is new Ada.Numerics.Discrete_Random(Wait);
   use Draw;
   G : Generator;
 
   protected Protected_Buffer is
      entry Add(I : in Integer);
      entry Take(I : out Integer);
   private 
      B : Buffer(1..Size);   
      Counter : Integer:=0;
      GetIndex : Integer:=1;
      PutIndex : Integer:=1;
         
 end Protected_Buffer;
   
   task Producer;
   task Consumer;
 
   protected body Protected_Buffer is
 
      entry Add(I : in Integer) when Counter < Size is
      begin
         B(PutIndex) := I;
         PutIndex := PutIndex mod Size + 1;
         Counter := Counter + 1;                   
         end Add;
 
      entry Take(I : out Integer) when Counter > 0 is
      begin
         I:=B(GetIndex);
         GetIndex := GetIndex mod Size + 1;  
         Counter := Counter - 1;      
         end Take;
   end Protected_Buffer;   
   
 
 
task body Consumer is
   Cons_Delay_Time : Integer;
begin
   loop
      Reset(G);
      Cons_Delay_Time:=Integer(Random(G));
      delay Duration(1.3*Float(Cons_Delay_Time));
      Protected_Buffer.Take(Cons_Delay_Time);
      Put_Line("Consumer : Taking "&Cons_Delay_Time'Img);
      delay 0.15;
    end loop;
end Consumer;
 
task body Producer is
   Prod_Delay_Time : Integer;
begin
   loop
      Reset(G);
      Prod_Delay_Time := Integer(Random(G));
      delay Duration(Prod_Delay_Time);
      Protected_Buffer.Add(Prod_Delay_Time);
      Put_Line("Producer : Adding "&Prod_Delay_Time'Img);
      delay 0.15;
   end loop;
end Producer;
 
begin
   null;
end Cyclic_Buffer;

Komentarze:
[11 - 14] Deklaracje generatora liczb pseudolosowych, który odpowiada za losowe czasy opóźnienia w działaniu obu zadań (od 0 do 5 sek).
[27 - 28] Uruchomienie wykonywania zadań
[55] Opóźnienie wykonywania zadania zależne od wylosowanej liczby
[56] Pobranie z bufora wartości do zmiennej lokalnej
[77] Pusta instrukcja programu - wszystkie instrukcje wykonują się w dwóch współbieżnych zadaniach

Kompilator: Ideone.com