среда, 04. мај 2016.

Rad sa taskovima u C# programskom jeziku


U prethodnom postu „Rad sa nitima u C# programskom jeziku“ je u najkraćim crtama sa čak 7 primera objašnjeno kako se radi sa nitima. Međutim, da bi ste razumeli niti i to je malo. Na internetu možete pronaći mnogo primera varijacija i upotrebe niti, dok možete i trebate eksperimentisati i sa vlastitim kombinacijama. Zato vam savetujem da mnogo samostalno eksperimentišete i testirate vaše vlastite metode pozivajući ih u nitima; u vašim vlastitim testovima i aplikacijama. To se isto odnosi i na taskove i na asinhronizovane metode i sve što učite. Takođe, morate da poznajete razliku i kad i šta treba da koristite. Na primer, pored upotrebe niti će te brzo shvatili da je mnogo bolje koristiti klasu ThreadPool nego klasu Threading pogotovo ako nameravate koristiti više niti sa dugotrajnim procesima ili kad više puta nameravate koristiti istu nit. Razlog je jednostavan, niti su expensive – skupi. Znači uzimaju mnogo memorije i vremena. Potrebno vam je 1MB za svaku nit koju koristite. Klasa Threading vam omogućava da kreirate niti na veoma niskom nivou, na nivou OS – Operativnog sistema. To znači da nad niti imate punu kontrolu, tj. možete da nit pokrenete, zaustavite ili prekinete.

 
( Taskovi su često mnogo bolje rešenje od upotrebe niti ) 

Za razliku od niti ThreadPool je delimično rešenje jer je mnogo jeftiniji; upamtite i da u ThreadPool-u svaka nit je uvek Background nit i da se niti u ThreadPool ne uništavaju kad se nit izvrši. Ali sa ThreadPool-om vi ne možete određivati prioritete izvršavanja niti, zato bi ste morali koristiti neke druge mehanizme; isto tako vi ne možete niti prekinuti u vreme izvršavanja. Kada koristite ThreadPool nemate kontrolu nad nitima kao sa klasom Threading jer ThreadPool radi na nivou CLR – Common Language Runtime-a. To znači i da broj niti koji možete izvršavati u ThreadPool-u zavisi od verzije .Net Framework-a kojeg koristite. Na primer ako koristite .NET Framework 4.0 onda možete koristiti 1 023 niti za 32-bit aplikacije ili 32 767 za 64-bit aplikacije. Međutim ako koristite .Net Framework 2.0 ograničeni ste na samo 25 niti prema procesoru. Isto tako vi faktički, najjednostavnije rečeno; samo pošaljete vaše zadatke u ThreadPool, kad se ThreadPool napuni vi samo trebate sačekati dok se ne oslobodi i nastavi sa izvršavanjem drugih zadataka. Vi čak možete i sami da odredite veličinu ThreadPool-a ali činjenica je da kontrolu nemate. Vi možete da pošaljete neki zadatak, ali ne možete sačekati da se završi ili da prihvatite neki rezultat. Zato postoji klasa Task koja rešava probleme i nedostatke i klase Threading i klase ThreadPool.

Kako da kreiramo taskove?


Postoji nekoliko načina kako da kreirate taskove. Zahvaljujući .Net Framework-u 4.0 vama je omogućena klasa Task koja predstavlja asinhronizovane operacije. Taskovi koriste niti iz ThreadPool-a ali vam nude odličnu fleksibilnost i kontrolu nad kreiranjem taskova. Postoje dve klase sa kojima će te kreirati taskove. Za taskove koje ne vraćaju nikakav rezultat koristite klasu Task dok za taskove koje vraćaju neki rezultat koristite klasu Task<TResult>. Taskove možete kreirati kreiranjem instance klase Task i pokretanjem taskova metodom Start, zatim korišćenjem statične metode TaskFactory.StartNew ili jednostavnije korišćenjem metode Task.Run. Vi takođe možete da koristite i mnogobrojne varijacije kontinuiranih metoda poput Task.WhenAll, Task.WhenAny, TaskFactory.ContinueWhenAll ili TaskFactory.ContinueWhenAny. Pogledajte neke od metoda klase Task:

ContinueWith – Kreira novi task koji će biti pokrenut asinhronizovano kada se tekući task kompletira.

Delay – Kreira novi task nakon odlaganja.

Run – Statična metoda koja šalje zahtev ThreadPool-u i vraća task objekat.

Start – Pokreće instancu taska, stavlja je na raspored i izvršavanje.

Wait – Čeka da se task kompletira.

WaitAll – Čeka sa se svi taskovi kompletiraju.

WaitAny – Čeka da bilo koji task kompletira parametre.

WhenAll – Statička metoda kreira task kada svi taskovi kompletiraju parametre.

WhenAny – Statička metoda kreira task kada bilo koji task kompletira parametre.



Sve mnogobrojne metode, propertije i klase u okviru imenskog prostora System.Threating.Task će te najbolje razumeti ukoliko iskodirate što više praktičnih primera. Sledeći primer pokazuje najjednostavnije kreiranje taska koji ne vraća rezultat. Obratite pažnju koliko je jednostavan kod.

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SimpleTask
{
    class Program
    {
        static void Main(string[] args)
        {
            Task t = Task.Run(() => {

                for (int i = 0; i < 10; i++)
                {
                    WriteLine($"Finished {i + 1} loop iterations.");

                }             
            });

            t.Wait();

            WriteLine(Environment.NewLine + "Press any key to continue...");
            ReadKey();

        }
    }
}

Kad pokrenete navedeni program, rezultat će biti sledeći:

Finished 1 loop iterations.
Finished 2 loop iterations.
Finished 3 loop iterations.
Finished 4 loop iterations.
Finished 5 loop iterations.
Finished 6 loop iterations.
Finished 7 loop iterations.
Finished 8 loop iterations.
Finished 9 loop iterations.
Finished 10 loop iterations.

Press any key to continue...

Kako navedeni program izgleda možete pogledati i na video-u:


( C# 6.0 Tutorial - Advanced - 13. A Simple Task )

Sad pogledajte jednostavan primer kada task treba da vrati neku vrednost.

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TaskReturnsValue
{
    class Program
    {
        static void Main(string[] args)
        {
            Task<string> t = Task.Run(() => {

                return "This returned from the task!";

            });
      
            WriteLine(t.Result);

            WriteLine(Environment.NewLine + "Press any key to continue...");
            ReadKey();

        }
    }
}

Kad pokrenete navedeni program, rezultat će biti sledeći:

This returned from the task!

Press any key to continue...


Kako navedeni program izgleda možete pogledati i na video-u:



( C# 6.0 Tutorial - Advanced - 14. A Task That Returns Value )

U prethodnom primeru task će forsirati nit da pročita rezultat pre nego što se task uništi i sve dok task se ne završi, to će blokirati trenutnu nit. Međutim ako vi želite da se druga operacija izvrši nad rezultatom najbolje je da pored taska koristite i njegovu metodu ContinueWith. Taj postupak zovemo Continuation – kontinuitet. Međutim to nije sve, već vi preko Overload – preopterećenja možete čak konfigurisati kada će se i kakav kontinuitet pokrenuti. Sledeći primer prikazuje najjednostavniji primer kontinuiteta.

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TaskContinutation
{
    class Program
    {
        static void Main(string[] args)
        {
            Task<Int32> t = Task.Run(() => {

                return 125;

            }).ContinueWith((i) => {

                return i.Result * 3;

            });

            WriteLine(t.Result);

            WriteLine(Environment.NewLine + "Press any key to continue...");
            ReadKey();

        }
    }
}

Kad pokrenete navedeni program, rezultat će biti sledeći:

375
 

Press any key to continue...
Kako navedeni program izgleda možete pogledati i na video-u:


( C# 6.0 Tutorial - Advanced - 15. Task Continutation )

Ali pogledajte sledeći primer; on pokazuje kako možete reagovati na redosled kontinuiteta.

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SchedulingContinuationTasks
{
    class Program
    {
        static void Main(string[] args)
        {
            {
                Task<Int32> t = Task.Run(() =>
                {
                    return 125;

                });

                t.ContinueWith((i) =>
                {
                    WriteLine("Task canceled!");

                }, TaskContinuationOptions.OnlyOnCanceled);

                t.ContinueWith((i) =>
                {
                    WriteLine("Task faulted!");

                }, TaskContinuationOptions.OnlyOnFaulted);

                var completedTask = t.ContinueWith((i) =>
                {
                    WriteLine("Task completed!");

                }, TaskContinuationOptions.OnlyOnRanToCompletion);

                completedTask.Wait();

                WriteLine(t.Result);

                WriteLine(Environment.NewLine + "Press any key to continue...");
                ReadKey();

            }
        }
    }
}

Kad pokrenete navedeni program, rezultat će biti sledeći:

Task completed!

125

Press any key to continue...


Kako navedeni program izgleda možete pogledati i na video-u:



( C# 6.0 Tutorial - Advanced - 16. Scheduling Continuation Tasks )

Međutim šta ako imate složeniju hijerarhiju, na primer task roditelja i druge potomke za koje želite da se izvrše prema određenom redosledu. Tada kontinuiranost može upotrebiti kao u sledećem primeru:

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AttachingTasks
{
    class Program
    {
        static void Main(string[] args)
        {
            Task<Int32[]> t = Task.Run(() =>
            {
                var results = new Int32[3];

                new Task(() => results[0] = 0, TaskCreationOptions.AttachedToParent).Start();
                new Task(() => results[1] = 1, TaskCreationOptions.AttachedToParent).Start();
                new Task(() => results[2] = 2, TaskCreationOptions.AttachedToParent).Start();

                return results;

            });

            var finalTask = t.ContinueWith(parentTask =>
           {
               foreach (int i in parentTask.Result)
               {
                   WriteLine("Task " + i);

               }

           });

            finalTask.Wait();
                 
            WriteLine(Environment.NewLine + "Press any key to continue...");
            ReadKey();

        }
    }
}

Kad pokrenete navedeni program, rezultat će biti sledeći:

Task 0
Task 1
Task 2

Press any key to continue...

Kako navedeni program izgleda možete pogledati i na video-u:



( C# 6.0 Tutorial - Advanced - 17. Attaching Tasks )
 
U prethodnom primeru ste morali da kreirate 3 taska sa istim opcijama. Da bi ste olakšali proces bolje je da u ovakvim situacijama koristite klasu TaskFactory. Ova klasa je mnogo fleksibilnija. Pogledajte sledeći primer.

using System;
using static System.Console;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace TaskFactory
{
    class Program
    {
        static void Main(string[] args)
        {
            Task[] t = new Task[2];
            String[] files = null;
            String[] dirs = null;
            String docsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

            t[0] = Task.Factory.StartNew(() => files = Directory.GetFiles(docsDirectory));
            t[1] = Task.Factory.StartNew(() => dirs = Directory.GetDirectories(docsDirectory));

            Task.Factory.ContinueWhenAll(t, completedTasks => {
                WriteLine("{0} contains: ", docsDirectory);
                WriteLine("   {0} subdirectories", dirs.Length);
                WriteLine("   {0} files", files.Length);

            });
          
            ReadKey();

        }
    }
}

Kad pokrenete navedeni program, rezultat će biti sličan u zavisnosti koliko imate foldera i fajlova u direktorijumu Documents u vašem računaru:

C:\Users\Manuel\Documents contains:
     16 subdirectories
       4 files

Kako navedeni program izgleda možete pogledati i na video-u:



( C# 6.0 Tutorial - Advanced - 18. Task Factory )

U slučaju da imate potrebu da kreirate više taskova i da vam ja potrebno da se svi taskovi izvrše pre nego što nastavite sa izvršavanjem vašeg programa, onda koristite metodu WaitAll. Pogledajte sledeći primer.

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TaskWaitAll
{
    class Program
    {
        static void Main(string[] args)
        {
            Task[] t = new Task[3];

            t[0] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                WriteLine("Task 1");
                return 1;

            });

            t[1] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                WriteLine("Task 2");
                return 2;

            });

            t[2] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                WriteLine("Task 3");
                return 3;

            });

            Task.WaitAll(t);

            WriteLine(Environment.NewLine + "Press any key to continue...");
            ReadKey();

        }
    }
}

Kad pokrenete navedeni program, rezultat će biti sledeći:

Task 1
Task 2
Task 3

Press any key to continue...


Kako navedeni program izgleda možete pogledati i na video-u:



( C# 6.0 Tutorial - Advanced - 19. Task WaitAll Method )

Ili vi možete odrediti da je dovoljno samo jedan task da se izvrši da bi se proces mogao nastaviti. Tada umesto metode WaitAll koristite metodu WaitAny. Pogledajte sledeći primer koji je isti kao prethodni sa malim izmenama i u kom se proces nastavlja čim se izvrši bilo koji task, ali mu je takođe dodata lista koja evidentira task koji je izvršen.

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;


namespace TaskWaitAny
{
    class Program
    {
        static void Main(string[] args)
        {
            Task<int>[] t = new Task<int>[3];

            t[0] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                WriteLine("Task 1");
                return 1;

            });

            t[1] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                WriteLine("Task 2");
                return 2;

            });

            t[2] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                WriteLine("Task 3");
                return 3;

            });

            while (t.Length > 0)
            {
                int i = Task.WaitAny(t);
                Task<int> completedTasks = t[i];

                WriteLine(completedTasks.Result);

                var temp = t.ToList();
                temp.RemoveAt(i);
                t = temp.ToArray();
               
            }

            WriteLine(Environment.NewLine + "Press any key to continue...");
            ReadKey();

        }
    }
}

Kad pokrenete navedeni program, rezultat će biti sličan:

Task 1
Task 2
1
2
Task 3
3

Press any key to continue...


Kako navedeni program izgleda možete pogledati i na video-u:



( C# 6.0 Tutorial - Advanced - 20. Task WaitAny Method )

Šta predstavlja Parallel klasa?

Kada radite sa imenskim prostorom System.Threating.Task vi imate i druge klase za rad sa taskovima poput klasa TaskSheduler, TaskShedulerExceptions, TaskStatus, UnobservedTaskExceptionEventArgs i Parallel klasu. Rad sa taskovima je velika i zahtevna materija da bi se moglo napisati cela knjiga samo na osnovu svih klasa, propertija i metoda koje taskovi koriste. Na vama ostaje da u potpunosti istražujete mnoge klase, propertije i stotine metoda i mnogobrojni varijacija korišćenja istih celog .Net Framework-a. Što se tiče Parallel klase, prva stvar koju treba da znate u vezi ove klase da ona može narušiti performanse programa, zato je neophodno da merite rezultate kad se isplati koristiti Parallel klasu i kada ne. Klasa Parallel ima odlične metode For, ForEach i Invoke koje možete koristiti za vaše paralelne procese. Pogledajte sledeći primer:
      
using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ParallelClass
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] colors = { "1. Red","2. Green", "3. Blue","4. Yellow" };

            WriteLine("Traditional foreach loop" + Environment.NewLine);
          
            // start the stopwatch for "for" loop
            var sw = Stopwatch.StartNew();
            foreach (string color in colors)
            {
                WriteLine($"{color}, Thread Id= {Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(100);

            }

            WriteLine();
            WriteLine($"Foreach loop execution time = {sw.Elapsed.TotalSeconds} seconds.");
            WriteLine();
            WriteLine("Using Parallel.ForEach");
            WriteLine();

            // start the stopwatch for "Parallel.ForEach"
            sw = Stopwatch.StartNew();
            Parallel.ForEach(colors, color =>
            {
                WriteLine($"{color}, Thread Id= {Thread.CurrentThread.ManagedThreadId}" + Environment.NewLine);
                Thread.Sleep(100);

            });

            WriteLine("Parallel.ForEach() execution time = {sw.Elapsed.TotalSeconds} seconds");
           
            WriteLine(Environment.NewLine + "Press any key to continue...");
            ReadKey();

        }
    }
}

Kad pokrenete navedeni program, rezultat će biti sličan u zavisnosti od vašeg računara.

Traditional foreach loop

1. Red, Thread Id= 9
2. Green, Thread Id= 9
3. Blue, Thread Id= 9
4. Yellow, Thread Id= 9

Foreach loop execution time = 0.4040142 seconds.

Using Parallel.ForEach

1. Red, Thread Id= 9

3. Blue, Thread Id= 10

2. Green, Thread Id= 11

4. Yellow, Thread Id= 9

Parallel.ForEach() execution time = 0.2158452 seconds

Press any key to continue...

Kako navedeni program izgleda možete pogledati i na video-u:



( C# 6.0 Tutorial - Advanced - 21. Parallel Class )