Sunteți pe pagina 1din 51

Fire de executie si sincronizare Pag.

1/51

Fire in .NET
Documentatie: MSDN si Joseph Albahari - www.albahari.com/nutshell/

Cuprins Partea a-I-a Procese. AppDomain. Fire. Creare. Clasa Thread. Partea a-II-a Sincronizare. Obiecte de sincronizare. Sincronizare fire din cadrul unui proces. Sincronizare fire din procese diferite.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 2/51

Fire Concepte generale


O aplicatie consta din unul sau mai multe procese. Un proces este un program in executie. Un proces mai este definit si ca un spatiu de memorie 4GB ce contine cod si date. .NET subdivide un proces in subprocese, numite domenii de aplicatii, reprezentate de
System.AppDomain.

Unul sau mai multe fire managed (create in .NET) reprezentate de System.Threading.Thread, pot rula in unul sau mai multe AppDomain din cadrul aceluaisi proces. Fiecare AppDomain este startat cu un singur fir ; codul din acest AppDomain poate crea alte AppDomain si fire. Un fir este unitatea de baza la care SO aloca timp procesor. Un fir poate executa orice parte de cod a unui proces, incluzand parti curente executate de alt fir. Firul are propria stiva, deci variabilele locale sunt gestionate separat. Un thread pool este o colectie de fire de lucru care executa asincron metode callback. Thread pool furnizeaza un management al firelor de lucru. C# suporta executia paralela a codului prin multithreading. Un program client C# (Console, WPF sau Windows Forms) este startat intr-un singur fir creat automat de CLR si de SO (fir principal, fir primar) si devine multifir prin crearea de fire aditionale. Un fir isi termina executia cand delegate-ul pasat in ctor Thread isi termina executia. O data terminat un fir nu poate fi restartat. Putem verifica daca un fir este in executie folosind proprietatea IsAlive. Spatiul de nume pentru fire este System.Threading. Observatie Firele partajeaza date daca au o referinta comuna la aceeasi instanta a obiectului. Variabilele locale sunt pe stiva. Campurile statice sunt partajate intre fire.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 3/51 Exemplu :


using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Threading;

namespace Fire_2011 { class MainFire_1 { // Variabila done este folosita in metoda Go pe // aceeasi instanta a clasei MainFire_1: mf // Prima data in firul creat, gestionat de metoda Go // si a doua oara in apelul metodei Go din firul principal bool done; // Metoda a instantei. Va fi folosita in firul principal si de // asemenea pentru a crea un fir. // Foloseste variabilele locale text si i void Go() { // Variabila tip referinta, este locala in cadrul firului string text = "Iasi"; // variabila tip valoare, e locala => pe stiva int i = 0; if (i == 0) Console.WriteLine("Nume fir = '{0}' ; i = {1}, done = {2}, + text = {3}", Thread.CurrentThread.Name, i, done, text); // Firul ce apeleaza aceasta metoda va fi starea Sleep // pentru 10 milisecunde. Thread.Sleep(10); // simulare activitate while (i < 10000) i++; Console.WriteLine("Nume fir = '{0}' ; i = {1}, done = {2}, + text = {3}", Thread.CurrentThread.Name,i, done, text); if (!done) { done = true; text = Thread.CurrentThread.Name; Console.WriteLine("Done. Nume fir : '{0}', done = {1}, + text = {2}", Thread.CurrentThread.Name, done, text); } } static void Main(string[] args) { Thread.CurrentThread.Name = "Fir principal";

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 4/51


MainFire_1 mf = new MainFire_1(); // creare instanta // Creare fir Thread f = new Thread(mf.Go); f.Name = "Fir 2"; // Lansare fir in executie f.Start(); // apel metoda Go din firul principal // acelasi cod se executa pe doua fire diferite mf.Go(); } } }

Rezultatul este (pe calculatorul meu):


Nume fir = 'Fir 2' ; i = Nume fir = 'Fir principal' ; i = Nume fir = 'Fir 2' ; i = Done. Nume fir : 'Fir 2', Nume fir = 'Fir principal' ; i = Press any key to continue . . . 0, done = False, text = Iasi 0, done = False, text = Iasi 10000, done = False, text = Iasi done = True, text = Fir 2 10000, done = True, text = Iasi

In exemplele ce urmeaza se considera ca am importat spatiile de nume System si System.Threading. Exemplu cu un camp static definit in clasa ce contine metoda firului (metoda a instantei).
class Main2 { // data membru statica, partajata static bool sdone; static void Main(string[] args) { Main2 m2 = new Main2(); Thread.CurrentThread.Name = "Fir principal"; // Creare fir si lansare Thread f = new Thread(m2.GoS); f.Name = "Fir 2"; f.Start(); // apel metoda GoS din firul principal m2.GoS(); } void GoS() { if (!sdone) { Console.WriteLine("GoS => {0}",

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 5/51


Thread.CurrentThread.Name); Thread.Sleep(10); // data membru din clasa, partajata sdone = true; } } } Rezultatul este : GoS => Fir 2 GoS => Fir principal Press any key to continue . . .

Ne-am astepta ca acest mesaj sa apara o singura data. Ce se intampla? In acest cod simplu e usor de explicat. Ce ne facem intr-un cod complex? Daca in metoda GoS schimbam ordinea instructiunilor din if astfel incat
sdone = true ;

sa fie inainte de Console.WriteLine(...) rezultatul este altul : GoS apare scris o singura data. Observatie Intr-un mediu multifir accesul la datele (resurse) partajate trebuie protejat. Explicatie Firul primar creaza si lanseaza Fir 2 care va fi luat in evidenta de procesul de planificare al firelor. Este lansat acest fir si apoi pus in asteptare (Thread.Sleep(10)) 10 milisecunde. Un fir blocat nu consuma resurse CPU. Este trecut in executie firul principal care se va opri si el in acelasi loc. A expirat timpul de asteptare pentru Fir 2 si isi reia executia dupa care se termina. In continuare isi reia executia firul principal si apoi se termina si acesta. Variabila sdone a fost actualizata cu aceeasi valoare de doua ori. Observatie Problema se rezolva folosind un obiect pentru sincronizare fire in cadrul metodei GoS. Fiind fire din cadrul aceluaisi proces putem folosi un lock sau Monitor (e sectiunea critica din Win32 API), care reprezinta acelasi lucru. Se protejeaza sectiunea de cod ce modifica resursa partajata. Codul ce este protejat astfel impotriva nedeterminarii intr-un context multifir se numeste threadsafe.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 6/51 Codul de mai sus poate fi modificat astfel (modificarile sunt cele scrise cu rosu) pentru a proteja sectiunea critica de cod:
class Main2 { static bool sdone; // Atentie: unde se declara obiectul folosit in blocare private object criticalSection = new object(); static void Main(string[] args) { Main2 m2 = new Main2(); Thread.CurrentThread.Name = "Fir principal"; Thread f = new Thread(m2.GoS); f.Name = "Fir 2"; f.Start(); m2.GoS(); } void GoS() { lock(criticalSection) { if (!sdone) { Console.WriteLine("GoS => {0}", Thread.CurrentThread.Name); Thread.Sleep(10); sdone = true; } } } }

O alta solutie poate fi aceea de a astepta in firul principal terminarea firului creat ( Fir 2 ) scriind urmatoarea linie de cod inainte de m2.GoS() si dupa f.Start();
f.Join();

si nu mai avem nevoie de lock si nici de variabila criticalSection. Observatie Thread.Sleep(0) sau Thread.Yield() dau o sansa si altor fire de a fi alocate pe procesor. Thread.Yield() se refera la firele ce ruleaza pe acelasi procesor.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 7/51

Creare si executie fire


Clasa responsabila pentru crearea firelor este Thread. Constructorii definiti in aceasta clasa au urmatoarele prototipuri (MSDN): Name
Thread(ParameterizedThreadStart) Thread(ThreadStart) Thread(ParameterizedThreadStart, Int32)

Thread(ThreadStart, Int32)

Description Initializes a new instance of the Thread class, specifying a delegate that allows an object to be passed to the thread when the thread is started. Initializes a new instance of the Thread class. Initializes a new instance of the Thread class, specifying a delegate that allows an object to be passed to the thread when the thread is started and specifying the maximum stack size for the thread. Initializes a new instance of the Thread class, specifying the maximum stack size for the thread.

Constructorul acestei clase are ca parametru un delegate ThreadStart definit astfel:


public delegate void ThreadStart();

Observatie In acest caz metoda pentru fir are prototipul void MetodaFir(), adica returneaza void si nu are parametri. Lansarea in executie a firului creat se face cu metoda Start().
Exemplu: class ThreadTest { static void Main() { Thread t = new Thread (new ThreadStart (Go)); // Lansare in executie metoda Go() pe noul fir. t.Start(); // Apel metoda Go() in firul principal. Go(); } static void Go() { Console.WriteLine ("Hello!"); }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 8/51


}

O alta posibilitate de creare a firului:


// Nu e nevoie sa folosim in mod explicit ThreadStart Thread t = new Thread (Go);

sau folosind expresii lambda sau metode anonime:


static void Main() { Thread t = new Thread ( () => Console.WriteLine ("Hello!") ); t.Start(); }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 9/51

Transferare date la un fir Expresii lambda


O modalitate de a transmite argumente la metoda firului este de a executa o expresie lambda ce apeleaza metoda cu argumentele dorite:
static void Main() { Thread t = new Thread ( () => Print ("Hello from t!") ); t.Start(); } static void Print (string message) { Console.WriteLine (message); }

sau
new Thread (() => { Console.WriteLine ("I'm running on another thread!"); Console.WriteLine ("This is so easy!"); }).Start();

Metode anonime
new Thread (delegate() { ... }).Start();

Pasare argument in metoda Start


static void Main() { Thread t = new Thread (Print); t.Start ("Hello from t!"); } static void Print (object messageObj) { string message = (string) messageObj; // Aici e nevoie de cast Console.WriteLine (message); }

Ctor Thread are doua prototipuri:


public delegate void ThreadStart(); public delegate void ParameterizedThreadStart (object obj);

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 10/51 Al doilea ctor accepta un argument de tip object. Trebuie sa facem cast la tipul asteptat in metoda firului. Observatie Cand folosim expresii lambda variabilele trebuie sa fie locale, in caz contrar rezultatul este nedeterminat. Exemplu
for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start();

Rezultatul poate fi:


0223567789

Problema este ca variabila i refera aceeasi locatie in timpul executiei buclei for. Rezolvarea consta in a defini o variabila temporara in bucla for.
for (int i = 0; i < 10; i++) { int temp = i; new Thread (() => Console.Write (temp)).Start(); }

In exemplul urmator avem aceeasi problema.


string text = "Prima atribuire"; Thread t1 = new Thread ( () => Console.WriteLine (text) ); text = "A doua atribuire"; Thread t2 = new Thread ( () => Console.WriteLine (text) ); t1.Start(); t2.Start(); Rezultat: A doua atribuire A doua atribuire

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 11/51

Fire foreground si background. Proprietatea IsBackground.


Implicit firele create sunt foreground, ceea ce inseamna ca acestea mentin aplicatia in viata pe timpul executiei lor, ceea ce nu se intampla pentru firele background. Cand aplicatia se termina (firul principal) orice fir background este terminat fortat. In aceasta situatie putem avea probleme la eliberarea resurselor. Daca e necesar sa eliberam resurse folosite de firul background putem folosi: Join sau o metoda din gama Wait. Observatie Faptul ca este fir foreground sau background nu are nici o legatura cu prioritatea de executie sau timpul alocat pentru executie. Cu ajutorul proprietatii IsBackground putem determina sau modifica starea firului (fir.IsBackground = true/false;).

Prioritatea firului
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }

Observatie Prioritatea firului este strans legata de clasa de prioritate a procesului. In .NET prioritatea procesului este descrisa astfel :
public enum ProcessPriorityClass { Idle, Normal, High, AboveNormal, BelowNormal, RealTime};

In Win32 API pentru clasele de prioritate ale unui proces au fost definite urmatoarele: (Clasele de prioritate ale proceselor din tabel lipsesc BelowNormal si AboveNormal) Clasa de prioritate Descriere IDLE_PRIORITY_CLASS Procesul ruleaz doar cnd sistemul este liber, de exemplu cnd nici un alt fie de execuie nu ateapt s fie executat. NORMAL_PRIORITY_CLASS Clasa de prioritate implicit. Procesul nu are nevoi speciale de planificare. HIGH_PRIORITY_CLASS Procesul primete o prioritate mai mare ca un proces IDLE_PRIORITY_CLASS sau NORMAL_PRIORITY_CLASS. REALTIME_PRIORITY_CLASS Procesul are cea mai mare prioritate posibil, mai ridicat chiar dect HIGH_PRIORITY_CLASS.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 12/51 Legatura dintre clasa de prioritate a unui proces si prioritatea firului este data in urmatorul tabel (in SO Vista, Windows 7, Windows 8 e posibil ca aceste valori sa fie modificate): Clasa de prioritate a procesului Prioritatea reltiv a firului Idle Below Normal Above Normal Normal Time-critical 15 15 15 15 Highest 6 8 10 12 Above normal 5 7 9 11 Normal 4 6 8 10 Below normal 3 5 7 9 Lowest 2 4 6 8 Idle 1 1 1 1

High RealTime 15 31 15 26 14 25 13 24 12 23 11 22 1 16

Tratare exceptii
Observatie Exceptiile vor fi tratate in metoda firului si nu in secventa de creare si lansare a firului. Exemplu eronat
public static void Main() { try { new Thread (Go).Start(); } catch (Exception ex) { // Nu se ajunge aici nicodata Console.WriteLine ("Exception!"); } }

Tratarea exceptiilor se va face in metoda Go.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 13/51

Thread Pooling
Contine fire preconstruite, care pot fi lansate mai repede de catre SO. Pattern-ul metodelor asincrone foloseste thread pool. Pentru a folosi thread pool .NET pune la dispozitie urmatoarele facilitati: TPL - Task Parallel Library (din Framework 4.0) Apel ThreadPool.QueueUserWorkItem Delegates asincroni
BackgroundWorker

Constructii indirecte ce folosesc thread pool: WCF Windows Communication Foundation, Remoting, ASP.NET, si ASMX Web Services servere de aplicatii System.Timers.Timer si System.Threading.Timer Metode din framework .NET care se termina in Async, cum ar fi cele din WebClient, si cele mai multe metode de tipul BeginXXX. PLINQ. Observatie Nu putem seta numele pentru un fir din thread pool. Firele din thread pool sunt numai background. Proprietatea Thread.CurrentThread.IsThreadPoolThread poate fi folosita pentru a determina daca suntem intr-un fir din thread pool.

Lansare fir din Thread Pool folosind TPL Task Parallel Library
Clasa Task
(.NET framework 4.0) Consultati MSDN.

Task lucreaza bine pe masini cu procesoare cu nuclee multiple. Exista in doua variante: non-

generica si generica. Putem considera clasa Task nongenerica ca un inlocuitor pentru


ThreadPool.QueueUserWorkItem, iar clasa generica Task<TResult> ca inlocuitor pentru

delegates asincroni. Exemplu:


static void Main() // Clasa Task este in System.Threading.Tasks { Task.Factory.StartNew (Go); }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 14/51


static void Go() { Console.WriteLine ("Hello from the thread pool!"); }

Task.Factory.StartNew returneaza un obiect Task, pe care-l putem folosi pentru a astepta

terminarea executiei. Clasa generica Task<Result> este o subclasa a clasei nongenerice Task. Aceasta ne permite sa preluam valoarea returnata de task dupa terminarea executiei.
Exemplu: static void Main() { // Lansare in executie metoda DownloadString Task<string> task = Task.Factory.StartNew<string> ( () => DownloadString ("http://www.linqpad.net") ); // Cod ce se va executa in paralel cu DownloadString RunSomeOtherMethod(); // Preluare rezultat. Daca metoda este inca in executie // firul curent se va bloca (intra in asteptare) pana // cand se termina DownloadString string result = task.Result; } static string DownloadString (string uri) { using (var wc = new System.Net.WebClient()) return wc.DownloadString (uri); }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 15/51

Introducere in TPL
Spatiul de nume System.Threading.Tasks. Clasele Task, Task<TResult>,TaskFactory<TResult> si Parallel. Task Parallel Library (TPL) este bazata pe conceptul de task. Termenul task parallelism se refera la unul sau mai multe task-uri ce ruleaza in mod concurent. Un task reprezinta o operatie asincrona si se aseamana cu crearea unui nou fir sau a unui fir de lucru din ThreadPool, dar furnizeaza urmatoarele posibilitati: Utilizarea resurselor sistem mai eficient si mai scalabil. Task-urile sunt puse intr-o coada la ThreadPool, care gestioneaza eficient aceste task-uri avand algoritmi pentru load balancing. Mai mult control asupra firelor. Task si framework-ul construit pentru acestea furnizeaza un API ce suporta asteptarea, intreruperea si continuarea executiei precum si tratarea execeptiilor, detalii de stare, planificare customizata.

Crearea si rularea implicita a task-urilor

Clasa Parallel
Furnizeaza suport pentru executia buclelor in paralel. Forma de baza a paralelismului este structurata prin urmatoarele metode statice din clasa Parallel (metodele sunt blocante): Metoda folosita este Parallel.Invoke ce accepta ca parametru un delegate Action.
Parallel.Invoke: Executa un tablou de delegates in paralel si apoi asteapta terminarea

acestora.
Parallel.For : Echivalentul paralel al buclei for din C#. Parallel.ForEach: Echivalentul paralel al buclei foreach din C#.

Parallel.Invoke
public static void Invoke (params Action[] actions); Invoke( Action []) Executa fiecare actiune, posibil in paralel. Invoke(ParallelOptions, Action []) - Executa fiecare actiune, posibil in paralel,

atata timp cat actiunea nu este anulata de catre utilizator.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 16/51 Exemplu:


Parallel.Invoke( () => DoSomeWork(), () => DoSomeOtherWork());

Exemplu
Parallel.Invoke ( () => new WebClient().DownloadFile ( "http://www.ipotesti.net", "Lacul.html"), () => new WebClient().DownloadFile ("http://www.ozana.net", "La scaldat.html"));

Parallel.For si Parallel.ForEach
Parallel.For si Parallel.ForEach sunt echivalente cu for si foreach din C# , dar

fiecare iteratie este executata in paralel. Declaratii


public static ParallelLoopResult For ( int fromInclusive, int toExclusive, Action<int> body) public static ParallelLoopResult ForEach<TSource> ( IEnumerable<TSource> source, Action<TSource> body)

Observatie Action este un delegate generic (contravariant) definit astfel:


public delegate void Action<in T>(T obj)

Incapsuleaza o metoda ce are un singur parametru si nu returneaza o valoare. Exemple


for (int i = 0; i < 100; i++) Foo (i);

este paralelizata astfel:


Parallel.For (0, 100, i => Foo (i));

sau mai simplu:

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 17/51


Parallel.For (0, 100, Foo); foreach (char c in "Hello, world") Foo (c); Parallel.ForEach ("Hello, world", Foo);

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 18/51 Exemplu MSDN (modificat metoda CreateWordArray si primul task din Invoke):
using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Threading; System.Threading.Tasks;

namespace PaskParalelism { class ParallelInvoke { static void Main() { // Creare array de string string[] words = CreateWordArray(); string longestWord = ""; #region ParallelTasks // Executa trei task-uri in paralel pe sursa words Parallel.Invoke( () => { Console.WriteLine("Incepe primul task..."); longestWord = GetLongestWord(words); }, // close first Action () => { Console.WriteLine("Incepe al doilea task..."); GetMostCommonWords(words); }, //close second Action () => { Console.WriteLine("Incepe al treilea task..."); GetCountForWord(words, "Iasi"); } //close third Action ); //close Parallel.Invoke Console.WriteLine("Returned from Parallel.Invoke"); #endregion Console.WriteLine("Press any key to exit"); Console.WriteLine("Fir principal: longestWord = {0}", longestWord); Console.ReadKey(); } #region HelperMethods private static void GetCountForWord(string[] words, string term) { var findWord = from word in words where

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 19/51


word.ToUpper().Contains(term.ToUpper()) select word; Console.WriteLine(@"Task 3 -- Cuvantul ""{0}"" apare de {1} ori.", term, findWord.Count()); } private static void GetMostCommonWords(string[] words) { var frequencyOrder = from word in words where word.Length > 6 group word by word into g orderby g.Count() descending select g.Key; var commonWords = frequencyOrder.Take(10); StringBuilder sb = new StringBuilder(); sb.AppendLine("Task 2 Cele mai comune cuvinate sunt:"); foreach (var v in commonWords) { sb.AppendLine(" " + v); } Console.WriteLine(sb.ToString()); } private static string GetLongestWord(string[] words) { var longestWord = (from w in words orderby w.Length descending select w).First(); Console.WriteLine("Task 1 Cel mai lung cuvant este {0}", longestWord); return longestWord; } static string[] CreateWordArray() { string s = "iasi,Vaslui, Botosani, Bacau, Suceava, Copou, Sarmisegetuza"; // Separate string into an array of words, // removing some common punctuation. return s.Split(new char[] { ',', '.'}, StringSplitOptions.RemoveEmptyEntries); } #endregion } }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 20/51

Crearea si rularea unui task in mod explicit


Un task este reprezentat de clasa System.Threading.Tasks.Task. Un task ce returneaza o valoare este reprezentat de clasa System.Threading.Tasks.Task<Result>, clasa derivata din Task.

Clasa Task
Instantele clasei Task pot fi create: Folosind proprietatea Factory ce returneaza o instanta pentru TaskFactory si in continuare metoda SartNew. Folosind un ctor al clasei Task si in continuare apelul metodei Start. Cand cream un task ii furnizam un delegate ce contine codul ce se va executa. Delegate-ul poate fi furnizat ca un delegate cu nume, metoda anonima sau o expresie lambda. Expresiile lamba pot contine apel la metode cu nume (vezi si exemplul anterior).
Exemplu:

var t = Task.Factory.StartNew(() => DoAction());


var taskA = new Task(() => Console.WriteLine("Hello from taskA.")); // Lansare taskA taskA.Start();

sau
// Un array de Task ce returneaza double // Metodele folosite sunt Do1, Do2, Do3
Task<double>[] taskArray = new Task<double>[] { Task<double>.Factory.StartNew(() => Do1()), // May be written more conveniently like this: Task.Factory.StartNew(() => Do2()), Task.Factory.StartNew(() => Do3()) }; // Preluare rezultate double[] results = new double[taskArray.Length]; for (int i = 0; i < taskArray.Length; i++) results[i] = taskArray[i].Result;

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 21/51 Posibilitati de preluare a rezultatului (MSDN):


// clasa Test trebuie sa fie definita in cod static void ReturnAValue() { // Returneaza un tip valoare Task<int> task1 = Task<int>.Factory.StartNew(() => 1); int i = task1.Result; // Returneaza un tip referinta, tip ce are un nume, // folosind expresii lambda Task<Test> task2 = Task<Test>.Factory.StartNew(() => { string s = ".NET"; double d = 4.0; return new Test { Name = s, Number = d }; }); Test test = task2.Result; // Returneaza un array dintr-o cerere PLINQ Task<string[]> task3 = Task<string[]>.Factory.StartNew(() => { string path = @"C:\users\public\pictures\"; string[] files = System.IO.Directory.GetFiles(path); var result = (from file in files.AsParallel() let info = new System.IO.FileInfo(file) where info.Extension == ".jpg" select file).ToArray(); return result; }); foreach (var name in task3.Result) Console.WriteLine(name); }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 22/51 sau (atentie la cuvantul cheie dynamic) :
class MyCustomData { public DateTime Date; public String Name; }
static void TaskDemo2() { // Create the task object by using an Action<object> to // pass in custom data. var taskB = Task.Factory.StartNew( (obj) => { MyCustomData data = (MyCustomData)obj; Console.WriteLine("Hello from {0}. Today is {1}.", data.Name, data.Date); }, new MyCustomData { Name = "taskB", Date = DateTime.Today } ); // Nu e nevoie sa cream o clasa cu nume pentru a // transmite datele : folosim cuvantul cheie dynamic var taskC = Task.Factory.StartNew( (state) => { // atentie la cuvantul cheie dynamic dynamic data = state; Console.WriteLine("TaskC : name : {0}, Date {1}, Time = {2}", data.Name, data.Date, data.Time); }, // tip anonim de unde preia datele new { Name = "taskC", Date = DateTime.Today, Time = 10 } ); }

urmat de apel TestDemo2() in metoda Main. Observatie: In .NET 4.5 apare metoda Run in clasa Task.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 23/51 Constructori pentru clasa Task (partial) MSDN
Name Task(Action) Task(Action, CancellationToken) Task(Action, TaskCreationOptions) Task(Action <Object >, Object) Task(Action, CancellationToken, TaskCreationOptions) Task(Action <Object >, Object, CancellationToken) Task(Action <Object >, Object, TaskCreationOptions) Task(Action <Object >, Object, CancellationToken, TaskCreationOptions)

Description Initializes a new Task with the specified action. Initializes a new Task with the specified action and CancellationToken while observing a cancellation token. Initializes a new Task with the specified action and creation options. Initializes a new Task with the specified action and state. Initializes a new Task with the specified action and creation options. Initializes a new Task with the specified action, state, and options. Initializes a new Task with the specified action, state, and options. Initializes a new Task with the specified action, state, and options.

Anularea : Cancellation
Posibilitatea de a anula executia unui task. In continuare exemplificam cateva metode de anulare a executiei. Tipurile CancellationTokenSource si CancellationToken. Ideea este de a pasa in ctor un obiect ce va da posibilitatea anularii executiei task-ului creat. Obiectul ce invoca o operatie ce poate fi anulata de exemplu prin crearea unui nou fir sau task, transmite acest token operatiei. Operatia invocata poate transfera acest obiect mai departe la alte operatii. Obiectul ce a creat acest token privitor la anularea unei operatii poate interoga mai tarziu operatia pentru a vedea ce s-a intamplat.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 24/51 Acest model de anulare al operatiilor are urmatoarele caracteristici: Un obiect ce invoca o operatie anulabila poate controla modul cand s-a cerut anularea operatiei. In momentul cand s-a cerut anularea operatiei, task-ul poate decide terminarea acesteia intr-un mod sigur (salvari de siguranta, eliberare resurse, etc.). Un listener poate asculta pe multiple tokens-uri in mod simultan. Listeners-ii pot fi notificati de cererea de anulare prin : inregistrari callback sau asteptari pe anumite evenimente. Anularea se refera la operatii, nu la obiecte. Cererea de anulare inseamna ca operatia ar trebui sa se opreasca cat de repede posibil dupa ce a realizat eliberarea resurselor. Un token de anulare se refera la o singura operatie de anulare, operatie ce trebuie implementata in program. Dupa ce proprietatea IsCancellationRequested a fost setata pe true (s-a apelat metoda Cancel() pe token de anulare), aceasta nu mai poate fi resetata, deci acest obiect de anulare nu poate fi refolosit dupa ce a fost anulat . Daca avem nevoie de un mecanism de anulare bazat pe un obiect vom inregistra pentru fiecare obiect un token de anulare. Exemplu: cancel la nivel de operatie si cancel la nivel de obiect
class CancelObject { public CancelObject() { Console.WriteLine("CancelObject ctor"); } public void Cancel() { Console.WriteLine("Metoda Cancel a fost apelata!"); } } static void Main(string[] args) { // Cancel la nivel de operatie CancellationTokenSource ctstop = new CancellationTokenSource(); ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), ctstop.Token); // cerere pentru cancel // Se doreste aprirea executiei metodei DoWork. ctstop.Cancel();

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 25/51


// Cancel la nivel de obiect CancelObject co = new CancelObject(); CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken token = cts.Token; Console.WriteLine("Fir principal"); CancelObject co2 = new CancelObject(); CancelObject co3 = new CancelObject(); // inregistrare metoda ce va fi apelata cand se cere // anularea "obiectului" token.Register(() => co.Cancel()); token.Register(() => co2.Cancel()); // cerere de terminare cts.Cancel(); } public static void DoWork(object obj) { CancellationToken token = (CancellationToken)obj; while(true) { if (token.IsCancellationRequested) { Console.WriteLine( "Operatie Cancel : metoda DoWork"); break; } Console.WriteLine(Execut DoWork); } }

Inregistrarea unei metode callback ce va fi apelata pe operatia de cancel


Metoda folosita este CancellationTokenRegister() ce returneaza un obiect CancellationTokenRegistration folosit in acest scop.
CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken token = cts.Token; WebClient wc = new WebClient(); // To request cancellation on the token // will call CancelAsync on the WebClient. token.Register(() => wc.CancelAsync()); Console.WriteLine("Starting request"); wc.DownloadStringAsync(new Uri("http://www.contoso.com")); cts.Cancel();

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 26/51

Clasa Task<TResult>
Reprezinta o operatie asincrona ce returneaza o valoare. Detalii complete in MSDN. Constructori
Name Task <TResult >(Func <TResult >) Task <TResult >(Func <TResult >, CancellationToken) Task <TResult >(Func <TResult >, TaskCreationOptions) Task <TResult >( Func <Object, TResult >, Object) Task <TResult >(Func <TResult >, CancellationToken, TaskCreationOptions) Task <TResult >( Func <Object, TResult >, Object, CancellationToken) Task <TResult >( Func <Object, TResult >, Object, TaskCreationOptions) Task <TResult >( Func <Object, TResult >, Object, CancellationToken, TaskCreationOptions)

Description Initializes a new Task <TResult > with the specified function. Initializes a new Task <TResult > with the specified function. Initializes a new Task <TResult > with the specified function and creation options. Initializes a new Task <TResult > with the specified function and state. Initializes a new Task <TResult > with the specified function and creation options. Initializes a new Task <TResult > with the specified action, state, and options. Initializes a new Task <TResult > with the specified action, state, and options. Initializes a new Task <TResult > with the specified action, state, and options.

Metode Description Creates a continuation that executes when the target Task ContinueWith(Action <Task >) completes. (Inherited from Task.) Runs the Task synchronously on the current TaskScheduler. RunSynchronously () (Inherited from Task.) Runs the Task synchronously on the TaskScheduler RunSynchronously(TaskScheduler) provided. (Inherited from Task.) Starts the Task, scheduling it for execution to the current Start () TaskScheduler. (Inherited from Task.) Start(TaskScheduler) Starts the Task, scheduling it for execution to the specified
Name

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 27/51 TaskScheduler. (Inherited from Task.) Waits for the Task to complete execution. (Inherited from Wait () Task.) Waits for the Task to complete execution. (Inherited from Wait(CancellationToken) Task.) Waits for the Task to complete execution. (Inherited from Wait(Int32) Task.) Waits for the Task to complete execution. (Inherited from Wait(TimeSpan) Task.) Waits for the Task to complete execution. (Inherited from Wait(Int32, CancellationToken) Task.) Observatie Atentie la prototipul metodei Wait(CancellationToken). Posibilitatea de a evita deadlock. Proprietati
Name AsyncState Factory Id IsCanceled IsCompleted IsFaulted Result Status

Description Gets the state object supplied when the Task was created, or null if none was supplied. (Inherited from Task.) Provides access to factory methods for creating Task <TResult > instances. Gets a unique ID for this Task instance. (Inherited from Task.) Gets whether this Task instance has completed execution due to being canceled. (Inherited from Task.) Gets whether this Task has completed. (Inherited from Task.) Gets whether the Task completed due to an unhandled exception. (Inherited from Task.) Gets the result value of this Task <TResult >. Gets the TaskStatus of this Task. (Inherited from Task.)

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 28/51

Proprietatea Factory
Furnizeaza acces la metodele pentru crearea instantelor Task<TResult>. Returneaza o instanta a clasei TaskFactory<TResult>. Ctor pentru TaskFactory<TResult> Description Initializes a TaskFactory <TResult > TaskFactory <TResult>() instance with the default configuration. TaskFactory <TResult> Initializes a TaskFactory <TResult > (CancellationToken) instance with the default configuration. Initializes a TaskFactory <TResult > TaskFactory <TResult> (TaskScheduler) instance with the specified configuration.
TaskFactory <TResult> (TaskCreationOptions, TaskContinuationOptions) TaskFactory <TResult> (CancellationToken, TaskCreationOptions, TaskContinuationOptions, TaskScheduler) Name

Initializes a TaskFactory <TResult > instance with the specified configuration. Initializes a TaskFactory <TResult > instance with the specified configuration.

Metodele cele mai importante din TaskFactory<TResult>:


StartNew(Func <TResult>) :

Creaza si lanseaza un Task<TResult>

Variante pentru aceasta metoda:


StartNew(Func StartNew(Func StartNew(Func StartNew(Func <TResult>, CancellationToken) <TResult>, TaskCreationOptions) <Object, TResult>, Object) <Object, TResult>, Object, CancellationToken)

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 29/51

Lansare fir din Thread Pool fara TPL


Metoda folosita este QueueUserWorkItem sau delegates asincroni. Delegates asincroni permit preluarea valorii de retur din metoda callback si returneaza exceptiile catre apelant.
QueueUserWorkItem :

Prototipuri pentru aceasta metoda (MSDN): Name


QueueUserWorkItem(WaitCallback) QueueUserWorkItem(WaitCallback, Object)

Description Queues a method for execution. The method executes when a thread pool thread becomes available. Queues a method for execution, and specifies an object containing data to be used by the method. The method executes when a thread pool thread becomes available.

Exemplu static void Main() { ThreadPool.QueueUserWorkItem (Go); ThreadPool.QueueUserWorkItem (Go, 123); Console.ReadLine(); } static void Go (object data) // data will be null with the first call. { Console.WriteLine ("Hello from the thread pool! " + data); } Rezultat: Hello from the thread pool! Hello from the thread pool! 123

Observatie In metoda Go trebuie sa facem cast la tipul dorit. Nu putem obtine rezultatul executiei metodei Go. Exceptiile trebuiesc tratate in metoda Go.
Delegates asincroni vezi cursul de la delagates.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 30/51

Sincronizare
Constructiile pentru sincronizare pot fi impartite in urmatoarele categorii: Metode simple de blocare
Sleep Join Task.Wait

Asteapta ca un fir sa-si termine executia sau sa nu lucreze o perioada de timp. Constructii cu blocari Blocari exclusive: permit accesarea de date comune fara a cauza interferente nedorite. Aceste constructii se realizeaza cu:
Monitor.Enter/Monitor.Exit Mutex SpinLock.

Blocari neexclusive:
Semaphore SemaphoreSlim blocari read/write (the reader/writer locks). Clasa ReaderWriterLock.

Constructii bazate pe semnalizare Permit ca un fir sa astepte pana primeste o notificare de la alt fir. Cele mai des utilizate sunt:
EventWaitHandle (clasele AutoResetEvent, ManualResetEvent) metodele Wait si Pulse.

Framework 4.0 introduce clasele


CountdownEvent Barrier ManualResetEventSlim.

Constructii de sincronizare neblocante Acestea protejeaza accesul la un camp comun. CLR si C# furnizeaza urmatoarele constructii neblocante: Asiminoaei Ioan

Fire de executie si sincronizare Pag. 31/51


Thread.MemoryBarrier Thread.VolatileRead Thread.VolatileWrite, cuvantul cheie volatile clasa Interlocked.

Blocarea
Un fir este blocat cand executia sa este trecuta in starea de pauza, aceasta inseamna Sleep sau asteptare pe un alt fir via Join sau EndInvoke. Un fir in aceasta stare nu consuma timp processor. Putem testa daca un fir este blocat astfel:
bool blocked = (someThread.ThreadState & ThreadState.WaitSleepJoin) != 0;

Cand un fir trece din starea blocat in starea neblocat sau invers, sistemul de operare realizeaza un context switch (de ordinal microsecundelor), trecerea dintr-o stare in alta.

Actiunea de blocare
Constructii cu lock si Mutex. Observatie: lock lucreaza cu tipuri referinta. Exemplu obiectul folosit pentru blocare este _locker:
class ThreadSafe { static readonly object _locker = new object(); static int _val1, _val2; static void Go() { lock (_locker) { if (_val2 != 0) Console.WriteLine (_val1 / _val2); _val2 = 0; } } }

Actiunea de blocare exclusiva este folosita pentru a asigura faptul ca numai un fir executa o sectiune de cod la un moment dat. lock folosit in fire din cadrul aceluiasi process. Mutex folosit in fire din cadrul aceluaisi process sau din procese diferite, in cazul din urma mutexul are un nume.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 32/51

Monitor.Enter - Monitor.Exit.

Monitor.TryEnter

Instructiunea lock este sinonima cu metodele Monitor.Enter si Monitor.Exit, cu un bloc try/finally. Exemplul de mai sus poate fi:
Monitor.Enter (_locker); // cod // aici ar putea aparea o exceptie try { if (_val2 != 0) Console.WriteLine (_val1 / _val2); _val2 = 0; } catch(...) { // cod } finally { Monitor.Exit (_locker); }

Observatie Daca intre Monitor.Enter si blocul try apare o exceptie este posibil ca Monitor.Enter sa se fi executat si atunci blocul finally nu se mai executa, deci nu avem eliberare resursa. Pentru a preveni acest lucru, in .NET 4.0 s-a adaugat:
public static void Enter (object obj, ref bool lockTaken);

unde al doilea parametru va avea valoarea true daca Monitor.Enter s-a executat. Codul de mai sus poate fi scris (de observat ca Monitor.Enter este in blocul try in acest caz) :
bool lockTaken = false; try { Monitor.Enter (_locker, ref lockTaken); // cod } catch(...) { // cod } finally { if (lockTaken) Monitor.Exit (_locker); }

Alegerea obiectului de sincronizare: obiectul trebuie sa fie tip referinta si sa fie vizibil tuturor firelor.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 33/51

Deadlocks
Exemplu
object locker1 = new object(); object locker2 = new object(); new Thread (() => { lock (locker1) { Thread.Sleep (1000); lock (locker2); // Deadlock } }).Start(); // cod in firul parinte : cel ce a lansat firul de mai sus lock (locker2) { Thread.Sleep (1000); lock (locker1); // Deadlock }

Exemplu care nu realizeaza blocarea dorita:


using System; using System.Threading; namespace FireSincronizare { class Program { static object _locker = new object(); static int n = 10; public static void Main(string[] args) { Console.WriteLine("Hello World!"); // MetodaF1 apeleaza Metoda2 in cadrul unei // constructii lock{...} Thread t1 = new Thread(new ThreadStart(MetodaF1)); t1.Start(); Thread.Sleep(0); n = 30; Console.WriteLine("Fir principal: {0}", AppDomain.GetCurrentThreadId()); // Metoda2 apelata direct din firul principal // In firul secundar este apelata din cadrul // unei constructii cu lock Metoda2();

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 34/51


//t1.Join(); Metoda2(); Console.WriteLine("Press any key to continue . . . "); Console.ReadKey(true); } public static void MetodaF1() { lock(_locker) { Console.WriteLine("MetodaF1. Id fir = {0} n = {1}",AppDomain.GetCurrentThreadId(),n); n = 20; Thread.Sleep(10); Metoda2(); // ne asteptam cumva ca n din Metoda2 sa fie // protejat la atribuire sau Metoda2 sa fie blocata? } } public static void Metoda2() { Console.WriteLine("Metoda2: Id fir= {0}, n = {1}",AppDomain.GetCurrentThreadId(), n); Thread.Sleep(10); n = 11; } } }

Rezultatul (urmarim evolutia valorilor lui n, in cod e scris italic):


Hello World! Fir principal: 2988 Metoda2: Id fir= 2988, n = 30 MetodaF1. Id fir = 3880 n = 30 Metoda2: Id fir= 2988, n = 11 Metoda2: Id fir= 3880, n = 11

// // // //

fir fir fir fir

principal secundar principal secundar

Press any key to continue . . .

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 35/51

Mutex
Mutex este asemanator cu lock, dar poate sincroniza fire din cadrul aceluiasi process sau din procese diferite. Pentru un Mutex SO ii ataseaza un proprietar. Clasa Mutex Constructori
Name Mutex() Mutex(Bolean)

Description
Initializes a new instance of the Mutex class with default properties.

Initializes a new instance of the Mutex class with a Boolean value that indi whether the calling thread should have initial ownership of the mutex. Initializes a new instance of the Mutex class with a Boolean value that indicates whether the calling thread should have initial ownership of the mutex, and a string that is the name of the mutex.

Mutex(Boolean, String)

Metode
Name Close()

Description
When overridden in a derived class, releases all resources held by the current WaitHandle. Releases all resources used by the current instance of the WaitHandle class. Releases the Mutex once. Blocks the current thread until the current WaitHandle receives a signal.

Dispose() ReleaseMutex() WaitOne()

Metodele folosite cu Mutex sunt: WaitOne pentru a bloca accesul ReleaseMutex pentru eliberare. Metodele Close() sau Dispose() fac automat ReleaseMutex.
ReleaseMutex este apelat numai de firul proprietar.

Exemplu de folosire a unui Mutex ce realizeaza o aplicatie singleton.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 36/51


class AppSingleton { static void Main() { // Avem nevoie de un Mutex cu nume using (var mutex = new Mutex (false, "Iasi_Copou")) { // Se asteapta cateva secunde pentru a preintampina // cazul cand aplicatia este in shuting down if (!mutex.WaitOne (TimeSpan.FromSeconds (3), false)) { Console.WriteLine ("Ruleaza o alta instanta a + aplicatiei."); return; } RunProgram(); } // eliberare Mutex } static void RunProgram() { Console.WriteLine ("Running. Press Enter to exit"); Console.ReadLine(); } }

Exemplu (MSDN)
// // // // This example shows how a Mutex is used to synchronize access to a protected resource. Unlike Monitor, Mutex can be used with WaitHandle.WaitAll and WaitAny, and can be passed across AppDomain boundaries.

using System; using System.Threading; class Test { // Create a new Mutex. The creating thread does not own the // Mutex. private static Mutex mut = new Mutex(); private const int numIterations = 1; private const int numThreads = 3; static void Main() { // Create the threads that will use the protected resource. for(int i = 0; i < numThreads; i++) { Thread myThread = new Thread(new ThreadStart(MyThreadProc)); myThread.Name = String.Format("Thread{0}", i + 1); myThread.Start();

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 37/51


} // The main thread exits, but the application continues to // run until all foreground threads have exited. } private static void MyThreadProc() { for(int i = 0; i < numIterations; i++) { UseResource(); } } // This method represents a resource that must be synchronized // so that only one thread at a time can enter. private static void UseResource() { // Wait until it is safe to enter. mut.WaitOne(); Console.WriteLine("{0} has entered the protected area", Thread.CurrentThread.Name); // Simulate some work. Thread.Sleep(500); Console.WriteLine("{0} is leaving the protected area\r\n", Thread.CurrentThread.Name); // Release the Mutex. mut.ReleaseMutex(); } }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 38/51

Semaphore
Clasele sunt :
Semaphore ; SemaphoreSlim (nou in .NET 4.0).

Metodele de asteptare si eliberare sunt :


Wait() ; Release().

The SemaphoreSlim class represents a lightweight, fast semaphore that can be used for waiting within a single process when wait times are expected to be very short. SemaphoreSlim relies as much as possible on synchronization primitives provided by the common language runtime (CLR). However, it also provides lazily initialized, kernel-based wait handles as necessary to support waiting on multiple semaphores. SemaphoreSlim also supports the use of cancellation tokens, but it does not support named semaphores or the use of a wait handle for synchronization. Un semafor poate fi asemanat ca o statie de spalat autoturisme: aceasta are o anumita capacitate; poate spala simultan un anumit numar de autoturisme dar nu mai mult decat capacitatea declarata. Semaforul este caracterizat prin doua argumente: unul indica capacitatea totala, iar celalalt indica numarul locurilor disponibile momentan. Observatie Semaforul cu capacitatea 1 este ca si un Mutex cu diferenta majora ca nu are un proprietar. Orice fir poate apela Release pe semaphore. Daca semaforul are nume poate fi folosit pentru sincronizare intre procese. Exemplu
class ServiceCar { static SemaphoreSlim _sem = new SemaphoreSlim (3); //Capacitate 3 static void Main() { for (int i = 1; i <= 5; i++) new Thread (Enter).Start (i); } static void Enter (object id) { Console.WriteLine (id + " doreste revizie"); _sem.Wait(); Console.WriteLine (id + " este in service!"); Thread.Sleep (1000 * (int) id); Console.WriteLine (id + " pleaca din service!"); _sem.Release();}}

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 39/51

Clasa AutoResetEvent
Folosita in sincronizarea firelor. Notifica un fir in asteptare ca a aparut un anumit eveniment. In .NET Framework 2.0, AutoResetEvent este derivata din clasa EventWaitHandle. Un AutoResetEvent este echivalent din punct de vedere functional cu EventWaitHandle creat cu EventResetMode.AutoReset. Metode: Metoda Set pentru a trece in starea semnalat. Metoda WaitOne il trece in starea nesemnalat daca era in starea semnalat, altfel se asteapta. Pot fi folosite si metodele statice WaitAll si WaitAny. Clasa nu poate fi mostenita.
public sealed class AutoResetEvent : WaitHandle

AutoResetEvent permite firelor sa comunice intre ele prin semnalizare. Aceasta comunicatie este legata de accesul la o resursa la care firele au nevoie de acces exclusiv. Un fir asteapta un semnal apeland WaitOne() pe AutoResetEvent. Daca AutoResetEvent este in starea nesemnalat, firul se blocheaza, asteapta ca firul ce controleaza acea resursa sa se termine sau sa treaca evenimentul in starea semnalat, firul din urma apeland metoda Set(). Cand obiectul devine semnalat, firul ce era in asteptare pe acel obiect va fi activat, si obiectul devine din nou nesemnalat. Daca nici un fir nu asteapta pe un obiect semnalat, acest obiect va ramane in starea semnalat. Starea initiala a unui AutoResetEvent poate fi controlata printr-o valoare pasata ctor : true = stare initiala semnalata false = stare initiala nesemnalata. AutoResetEvent poate fi folosit cu metodele statice WaitAll si WaitAny. Metoda WaitHandle.WaitAll. Prototipuri.
public static bool WaitAll(WaitHandle[]);

Asteapta ca toate elementele din tabloul de handle specificati sa primeasca un semnal.


public static bool WaitAll(WaitHandle[], int, bool);

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 40/51 Asteapta ca toate elementele din tabloul specificat sa primeasca un semnal, folosind in plus o valoare Int32 pentru a masura un interval de timp, si daca sincronizarea sa se termine inainte de asteptare.
public static bool WaitAll(WaitHandle[], TimeSpan, bool);

Acelasi lucru ca mai sus numai ca se foloseste a valoare TimeSpan.

Observatie WaitAny are aceeasi functionalitate ca si WaitAll, diferenta fiind ca se asteapta ca cel putin un element din tablou sa devina semnalat.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 41/51

ManualResetEvent
Evenimente cu resetare manuala. Metode:
Reset() pentru a trece evenimentul in starea nesemnalat. Firul care asteapta foloseste metoda WaitOne() pe eveniment. Un fir trece event in starea semnalat folosind metoda Set().

Metode importante din aceasta clasa. Sets the state of the specified event to nonsignaled. Set Sets the state of the specified event to signaled. WaitOne (inherited from WaitHandle) Overloaded. When overridden in a derived class, blocks the current thread until the current WaitHandle receives a signal. Exemplu: de urmarit metodele Set si WaitAll
using using using using System; System.IO; System.Security.Permissions; System.Threading; Reset

// Request permission to create and write files to C:\TestTest. [assembly: FileIOPermissionAttribute(SecurityAction.RequestMinimum, All = "C:\\TestTest")] class Test { static void Main() { const int numberOfFiles = 5; string dirName = "C:\\TestTest"; string fileName; byte[] byteArray; Random randomGenerator = new Random(); // se declara un array de 5 evenimente manuale ManualResetEvent[] manualEvents = new ManualResetEvent[numberOfFiles]; // Informatie ce o pasez firului. // Clasa State este definita mai jos in cod.

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 42/51


State stateInfo; if(!Directory.Exists(dirName)) { Directory.CreateDirectory(dirName); } // Queue the work items that create and write to the files. for(int i = 0; i < numberOfFiles; i++) { fileName = string.Concat( dirName, "\\Test", i.ToString(), ".dat"); // Create random data to write to the file. byteArray = new byte[1000000]; randomGenerator.NextBytes(byteArray); // se creaza evenimentul in starea nesemnalat manualEvents[i] = new ManualResetEvent(false); stateInfo = new State(fileName, byteArray, manualEvents[i]); // Lansare fir din ThreadPool. // Metoda pentru fir este in clasa Writer. ThreadPool.QueueUserWorkItem( new WaitCallback(Writer.WriteToFile), stateInfo); } // Since ThreadPool threads are background threads, // wait for the work items to signal before exiting. if(WaitHandle.WaitAll( manualEvents, new TimeSpan(0, 0, 5), false)) { Console.WriteLine("Files written - main exiting."); } else { // The wait operation times out. Console.WriteLine("Error writing files - main exiting."); } } } // Maintain state to pass to WriteToFile. class State { public string fileName; public byte[] byteArray; public ManualResetEvent manualEvent; public State(string fileName, byte[] byteArray,

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 43/51


ManualResetEvent manualEvent) { this.fileName = fileName; this.byteArray = byteArray; this.manualEvent = manualEvent; } } class Writer { static int workItemCount = 0; Writer() {} // // Executata de fir (avem thread pool = fire generate de sistem). // public static void WriteToFile(object state) { int workItemNumber = workItemCount; Interlocked.Increment(ref workItemCount); Console.WriteLine("Starting work item {0}.", workItemNumber.ToString()); // // Preluare informatii pentru fir // Este obligator cast // State stateInfo = (State)state; FileStream fileWriter = null; // Create and write to the file. try { fileWriter = new FileStream( stateInfo.fileName, FileMode.Create); fileWriter.Write(stateInfo.byteArray, 0, stateInfo.byteArray.Length); } finally { if(fileWriter != null) { fileWriter.Close(); } // Signal Main that the work item has finished. Console.WriteLine("Ending work item {0}.", workItemNumber.ToString()); stateInfo.manualEvent.Set(); } } }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 44/51 Exemplu pentru WaitOne. Se calculeaza suma a trei numere (MSDN).
using System; using System.Threading; class CalculateTest { static void Main() { Calculate calc = new Calculate(); Console.WriteLine("Result = {0}.", calc.Result(234).ToString()); Console.WriteLine("Result = {0}.", calc.Result(55).ToString()); } } class Calculate { double baseNumber, firstTerm, secondTerm, thirdTerm; AutoResetEvent[] autoEvents; ManualResetEvent manualEvent; // Generate random numbers to simulate the actual calculations. Random randomGenerator; public Calculate() { autoEvents = new AutoResetEvent[] { new AutoResetEvent(false), new AutoResetEvent(false), new AutoResetEvent(false) }; manualEvent = new ManualResetEvent(false); } void CalculateBase(object stateInfo) { baseNumber = randomGenerator.NextDouble(); // Signal that baseNumber is ready. manualEvent.Set(); } // The following CalculateX methods all perform the same // series of steps as commented in CalculateFirstTerm. void CalculateFirstTerm(object stateInfo) { // Perform a precalculation. double preCalc = randomGenerator.NextDouble(); // Wait for baseNumber to be calculated. manualEvent.WaitOne();

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 45/51


// Calculate the first term from preCalc and baseNumber. firstTerm = preCalc * baseNumber * randomGenerator.NextDouble(); // Signal that the calculation is finished. autoEvents[0].Set(); } void CalculateSecondTerm(object stateInfo) { double preCalc = randomGenerator.NextDouble(); manualEvent.WaitOne(); secondTerm = preCalc * baseNumber * randomGenerator.NextDouble(); autoEvents[1].Set(); } void CalculateThirdTerm(object stateInfo) { double preCalc = randomGenerator.NextDouble(); manualEvent.WaitOne(); thirdTerm = preCalc * baseNumber * randomGenerator.NextDouble(); autoEvents[2].Set(); } public double Result(int seed) { randomGenerator = new Random(seed); // Simultaneously calculate the terms. ThreadPool.QueueUserWorkItem( new WaitCallback(CalculateBase)); ThreadPool.QueueUserWorkItem( new WaitCallback(CalculateFirstTerm)); ThreadPool.QueueUserWorkItem( new WaitCallback(CalculateSecondTerm)); ThreadPool.QueueUserWorkItem( new WaitCallback(CalculateThirdTerm)); // Wait for all of the terms to be calculated. WaitHandle.WaitAll(autoEvents); // Reset the wait handle for the next calculation. manualEvent.Reset(); return firstTerm + secondTerm + thirdTerm; }}

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 46/51

Pattern Asyncron bazat pe evenimente. BackgroundWorker. [Event-based Asynchronous Pattern (EAP)]


Trasaturi Model cooperativ de anulare (o actiune poate fi intrerupta) Abilitatea de a actualiza WPF si Windows Forms cand s-a terminat actiunea. Forward exceptii catre evenimentul de completare al actiunii. Toate aceste lucruri trebuiesc scrise de programator. Pentru acest pattern in .NET exista doua clase importante : BackgroundWorker si WebClient. Clasa WebClient: furnizeaza metode pentru a trimite si primi date de la o resursa identificata printr-un URI. Exemplu MSDN:
using System; using System.Net; using System.IO; public class Test { public static void Main (string[] args) { if (args == null || args.Length == 0) { throw new ApplicationException ( "Specify the URI of the resource to retrieve."); } WebClient client = new WebClient (); // Add a user agent header in case the // requested URI contains a query. client.Headers.Add ("user-agent", "Mozilla/4.0 (compatible; + MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"); Stream data = client.OpenRead (args[0]); StreamReader reader = new StreamReader (data); string s = reader.ReadToEnd (); Console.WriteLine (s); data.Close (); reader.Close (); } }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 47/51

Clasa BackgroundWorker
The BackgroundWorker type exposes the following members.

Constructor

Name
BackgroundWorker

Description
Initializes a new instance of the BackgroundWorker class.

Proprietati
Name WorkerReportsProgress

Description
Gets or sets a value indicating whether the BackgroundWorker can report progress updates. Gets or sets a value indicating whether the BackgroundWorker supports asynchronous cancellation.

WorkerSupportsCancellation

Metode
Name OnDoWork OnProgressChanged OnRunWorkerCompleted ReportProgress(Int32) ReportProgress(Int32, Object) RunWorkerAsync() RunWorkerAsync(Object)

Description
Raises the DoWork event. Raises the ProgressChanged event. Raises the RunWorkerCompleted event. Raises the ProgressChanged event. Raises the ProgressChanged event. Starts execution of a background operation. Starts execution of a background operation.

Evenimente
Name Disposed

Description
Occurs when the component is disposed by a call to the Dispose

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 48/51 method.


DoWork ProgressChanged RunWorkerCompleted

Occurs when RunWorkerAsync is called. Occurs when ReportProgress is called. Occurs when the background operation has completed, has been canceled, or has raised an exception.

Exemplu: BackgroundWorker
Folosire BackgroundWorker: pasii minimi necesari sunt comentati in cod
using using using class { System; System.Threading; System.ComponentModel; Program static BackgroundWorker _bw; static void Main() { // Pas 1. Creare BW _bw = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true }; // Pas 2. Tratez evenimentul DoWork metoda bw_DoWork _bw.DoWork += bw_DoWork; // Pas 3. Eveniment ProgressChanged _bw.ProgressChanged += bw_ProgressChanged; // Pas 4. Eveniment actiune terminata _bw.RunWorkerCompleted += bw_RunWorkerCompleted; // Pas 5. Lansare fir backgroundWorker _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(); }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 49/51


// Metoda pentru BackgroundWorker 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); } static void bw_ProgressChanged (object sender, ProgressChangedEventArgs e) { Console.WriteLine ("Reached " + e.ProgressPercentage + "%"); } }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 50/51

Problema fire
Presupunem urmatorul scenariu. La nivelul unei aplicatii in C# exista o lista dubla inlantuita. Se vor efectua operatii (adaugare, eliminare, modificare nod) asupra acestei liste din fire diferite din cadrul aceluiasi proces. Fiecare fir va avea acces la urmatoarele informatii : lista ; nodul de inceput al listei ; nodul final al listei. Se vor crea minimum trei fire (in afara firului principal) ce vor executa aceste operatii. Va imaginati scenariul pentru efectuarea diverselor operatii (adaugare, eliminare, modificare). In final firul principal va afisa lista, adica fiecare nod cu valoarea respectiva precum si lista cu nodurile eliminate. Nodul listei ar putea avea urmatoarea structura (campurile obligatorii se pastreaza): class Nod { string Valoare ; // camp obligatoriu string Key ; // camp obligatoriu; valoare unica pentru identificare nod Nod next ; // camp optional; legatura la nodul urmator Nod prev; // camp optional; legatura la nodul anterior } Campul Valoare va contine numele firului care a creat / modificat acel nod si momentul de timp cand a fost efectuata operatia. Acest camp va mentine informatia in mod cumulativ, adica la valoarea existenta se va adauga (concatena) noua valoare (se aplica pentru operatiile de modificare si eliminare nod). Operatia de eliminare va salva nodurile eliminate in alta lista. Idei de implementare (nu e obligatoriu sa le folositi). Metoda pentru fir poate sa execute cod intr-o bucla while controlata de o variabila numita CanContinue, variabila gestionata de firul principal : Codul ar putea fi scris astfel : ... while(CanContinue) { // cod pentru determinare operatie (adaugare, eliminare, etc.) // cod pentru operatia selectata }

Asiminoaei Ioan

Fire de executie si sincronizare Pag. 51/51 sau cod executat intr-o bucla for (nr de noduri sa nu fie mai mare de 5). La lansarea firului variabila CanContinue va fi setata pe true. Setarea pe false a acestei variabile va duce la terminarea firului / firelor. Daca optati pentru bucla for nu mai e nevoie de variabila CanContinue. Pentru lista dubla inlantuita puteti folosi LinkedList generica. In acest caz primul si ultimul nod il puteti lua din proprietati. Studiati metodele expuse de acest tip (LinkedList) pentru a le folosi in operatiile cerute.

Asiminoaei Ioan

S-ar putea să vă placă și