Your SlideShare is downloading. ×
0
Diverse C# 4.0     2012
Event-uri si thread-safety   public class Class1     {         public event EventHandler MyEvent;         protected vo...
Event-uri si thread-safety   Modificarea unui event din alt thread este thread-safe. Compilator-ul    emite metode cu atr...
Event-uri si thread-safety   lock(this) si lock(typeof(T)) nu sunt safe deoarece nu controlam in mod    exclusiv obiectul...
Event-uri si thread-safety   In .NET 4.0 compilatorul genereaza cod thread-safe mai robust pentru    atasarea/dezatasarea...
Thread-safety and volatile   Compilatorul JIT optimizeaza agresiv in anumite configuratii de build. Din nefericire    opt...
Thread-safety and volatile   Compilatorul JIT cu target x86 si optimize on, utilizeaza cea mai agresiva optimizare.    pr...
Thread-safety and volatile   Main creaza un thread si executa metoda Worker. Worker incrementeaza variabila i la infinit....
Thread-safety and volatile   Aplicatia anterioara va functiona corect in toate situatiile daca marcam campul flag ca si  ...
Locking  ReaderWriterLock                vs ReaderWriterLockSlim vs lock    Un readerwriterlock permite mai multe thread...
Locking  ReaderWriterLock                vs ReaderWriterLockSlim vs lock    Un readerwriterlock permite mai multe thread...
ThreadLocal<T> vs ThreadStatic   Campurile marcate cu ThreadStatic sunt initializate in constructorul static,    care se ...
ThreadLocal<T> vs ThreadStatic   Un camp poate fi de tipul ThreadLocal<T> in ambele cazuri: static sau per    instanta. ...
dynamic     Static typing – erorile sunt detectate la momentul compilarii, compilatorul genereraza cod      compact si ef...
dynamic – exemplu 1class DynamicXml : DynamicObject   {         public override bool TryGetMember(GetMemberBinder binder, ...
dynamic – exemplu 1                                                      <?xml version="1.0" encoding="utf-8" ?>dynamic xm...
dynamic – exemplu 1                                                        <Project ToolsVersion="4.0" DefaultTargets="Bui...
ThreadPool   Exista un overhead legat de initializarea si pornirea unui thread: thread    kernel object, alocare Thread e...
ThreadPool net 3.5
ThreadPool algorithms   Clr ajusteaza in mod automat numarul de thread-uri in functie de rata de finalizarea a task-    u...
ThreadPool algorithms
ThreadPool algorithms
ThreadPool algorithms
Limitari ThreadPool   Nu exista notificari de terminare a task-urilor .   Nu exista un mecanism de comunicare a exceptii...
Limitari ThreadPool (WCF)   WCF executa request-urile folosind I/O threads si nu worker threads.   In figura se evidenti...
Limitari ThreadPool (WCF)   O solutie recomandata de Microsoft este extinderea WCF cu un custom operation behavior care s...
Task Parallel Library (TPL)   Reprezinta o colectie de clase ce ofera un API mai bogat pentru rularea de task-uri de    g...
Imbunatatiri ThreadPool 4.0/Tasks   In afara de lista globala de task-uri fiecare worker thread isi mentine o lista inter...
Imbunatatiri ThreadPool 4.0/Tasks
Imbunatatiri ThreadPool 4.0/Tasks
Imbunatatiri ThreadPool 4.0/Tasks   Task-urile din lista locala sunt executate in regim LIFO. Probabilitatea ca    datele...
Imbunatatiri ThreadPool 4.0/Tasks   In cazul in care un worker thread este idle, se verifica daca lista globala contine e...
Task, Task<T>   Reprezinta o abstractizare la un nivel mai inalt decat acela de Thread.   Un task poate fi sau nu rulat ...
Parent/Child Task   Apelul Wait pentru parinte se finalizeaza atunci cand toate task-uri copii au fost deasemenea finaliz...
Wait pentru mai multe task-uri      var t1 = Task.Factory.StartNew(DoOperation1);      var t2 = Task.Factory.StartNew(DoOp...
Cancelarea task-urilor var cts = new CancellationTokenSource(); CancellationToken token = cts.Token; cts.CancelAfter(500);...
Continuations
Continuations   ContinueWith executa un task dupa ce un alt task este finalizat. Un task se poate termina cu    success, ...
Continuations   O alta modalitate de a trata exceptiile este aceea de a inlantui continuari diferite pentru stari    dife...
Gotcha continuations   Daca un continuation nu se executa datorita optiunilor, nu este “ignorata” ci este considerata    ...
Task si exceptiile   Pornind de la comportamentul default in NET 4.0 prin care orice exceptie netratata intr-un    thread...
Exemplu .NET 4.5    Task op1 = PerformOperation1();    Task op2 = PerformOperation2();    await op1;    await op2;   Daca...
Async/Awaitprivate int longRunning(){    Thread.Sleep(1000);    return 5;}private void button1_Click(object sender, EventA...
Async/Await   Metoda care va fi rulata asincron trebuie intoarca void, Task, sau Task<T>. Metoda apelanta care foloseste ...
Async/Await
Async/Await   Nu doar pentru obiecte de tipul Task se poate apela await. Se poate apela await pentru orice tip care detin...
Async/Await   Await nu se poate aplica unui apel de metoda lista de task-uri. Folosind metode de extensie putem scrie:   ...
Async/Await   Putem crea un awaiter pentru orice element care ofera notificari cu privire la finalizarea crearii sale sau...
Async/Await   Exemplu de awaiter custom: extindem await pentru controale windows forms. Codul care urmeaza apelului    aw...
Async/Await   Exemplu de awaiter custom: extindem await pentru controale windows forms. Codul care urmeaza apelului    aw...
Exceptii in Task.Wait vs await   In cazul Task.Wait erorile sunt intotdeauna imbracate intr-o AggregateException.   In ....
Async/Await Gotchas 1   Fie o functie WriteFileAsync   Utilizare:   In unele cazuri codul va functiona, in alte cazuri ...
Async/Await Gotchas 1   Functia WriteFileAsync trebui modificata astfel:   Utilizare corecta
Async/Await Gotchas 2   Scrierea unui event handler async. Exemplu: in Windows 8 pentru implementarea data sharing intre ...
Async/Await Gotchas 3   Tentatia de a apela metoda Wait() avand ca argument un Task returnat de o metoda async. Rezultatu...
Parallel programming PFX   In cazul computerelor de azi avem in mod obisnuit mai multe core-uri. Pentru a beneficia de in...
Parallel Linq (PLINQ)   Plinq reprezinta cel mai facil mod de paralelizare a task-urilor.   Se foloseste extensia AsPara...
PLINQ si ordonarea   Un efect colateral al paralelizarii executiei este acela ca rezultatul obtinut    dupa reuniunea rez...
PLINQ Cancellation  var million = Enumerable.Range(3, 1000000);  var cancelSource = new CancellationTokenSource();  var pr...
PLINQ Optimizari   Unul din avantajele PLINQ este acela ca se ocupa de agregarea partitiilor in lista    finala. De multe...
PLINQ modalitati de partitionare   Range partitioning       Functioneaza cu surse de date care implementeaza Ilist, ILis...
PLINQ   LIMITARI       Functioneaza doar pentru provideri linq locali. Nu functioneaza pentru LinqToSql, EF sau alti    ...
Parallel.For si Parallel.Foreach      Folosirea index-ului curentParallel.ForEach ("Hello, world", (c, state, i) =>{     ...
Parallel.For si Parallel.Foreach   Oprirea completa       Parallel.ForEach ("Hello, world", (c, loopState) =>       {    ...
Parallel.For si Parallel.Foreach   Optimizarea concatenarii rezultatelor buclei folosind variabile locale pentru    fieca...
Upcoming SlideShare
Loading in...5
×

Curs c#

663

Published on

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
663
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
24
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Transcript of "Curs c#"

  1. 1. Diverse C# 4.0 2012
  2. 2. Event-uri si thread-safety public class Class1 { public event EventHandler MyEvent; protected void RaiseMyEvent(EventArgs e) { var handler = MyEvent; //multicast delegate-ul este imutabil. copierea este safe, desi poate fi out-of-date //din cauza faptului ca storage-ul pentru event nu este volatile if (handler != null) handler(this, e); } protected void RaiseMyEventBad(EventArgs e) { if (MyEvent != null) //daca un alt thread dezataseaza ultimul eventhandler aici, MyEvent devine null MyEvent(this, e); } }
  3. 3. Event-uri si thread-safety Modificarea unui event din alt thread este thread-safe. Compilator-ul emite metode cu atributul synchronized desi echipa CLR nu recomanda folosirea: lock(this) si lock(typeof(T))
  4. 4. Event-uri si thread-safety lock(this) si lock(typeof(T)) nu sunt safe deoarece nu controlam in mod exclusiv obiectul pe care aplicam lock. System.Type poate fi partajat intre mai multe AppDomain, si executia unui lock(typeof(T)) in alt Application domain poate interfera cu executia codului din Application Domain-ul curent.
  5. 5. Event-uri si thread-safety In .NET 4.0 compilatorul genereaza cod thread-safe mai robust pentru atasarea/dezatasarea evenimentelor folosind o tehnica lock free
  6. 6. Thread-safety and volatile Compilatorul JIT optimizeaza agresiv in anumite configuratii de build. Din nefericire optimizarea se face cu prezervarea intentiei codului doar din punct de vedere al unui singur fir de executie. private static void OptimizedAway() { // expresie constanta cunoscuta la compilare, are valoarea 0. var value = (1 * 100) - (50 * 2); // daca valoarea este 0 bucla nu se executa for (Int32 x = 0; x < value; x++) { // acest cod nu e compilat doarece bucla nu se executa Console.WriteLine("Jeff"); } } Pentru OptimizedAway se poate incerca inlining, cum metoda este vida, toate apelurile metodei vor fie eliminate la compilare.
  7. 7. Thread-safety and volatile Compilatorul JIT cu target x86 si optimize on, utilizeaza cea mai agresiva optimizare. private static bool flag = false; public static void Main(string[] args) { Console.WriteLine("Main: letting worker run for 5 seconds"); var t = new Thread(Worker); t.Start(); Thread.Sleep(5000); flag = true; //avem iluzia ca am semnalat corect oprirea catre worker thread Console.WriteLine("Main: waiting for worker to stop"); t.Join(); Console.WriteLine("End. Press any key..."); Console.ReadKey(); } private static void Worker(Object o) { Int32 x = 0; while (!flag) x++; Console.WriteLine("Worker: stopped when x={0}", x); } Acest program nu se termina.
  8. 8. Thread-safety and volatile Main creaza un thread si executa metoda Worker. Worker incrementeaza variabila i la infinit. Main permite thread-ului sa ruleze 5 secunde pana cand semnalizeaza oprirea setand campul boolean flag pe valoarea true. Worker thread trebuie sa afiseze valoarea la care a ajuns variabila i, iar apoi thread-ul isi va termina executia. Main asteapta finalizarea thread- ului folosind metoda Join. Cand Worker este compilat, JIT vede faptul ca flag are valori discrete: true/false si nu se schimba in interior-ul functiei Worker. Compilatorul va produce cod care verifica daca flag este true, si daca da, afiseaza valoarea 0 si se termina. Daca flag este false atunci se genereaza acest loop infinit. Se elimina prin optimizare citirea valorii flag pentru fiecare iteratie.In acest fel bucla va rula teoretic mai rapid. In aceasta situatie, Main seteaza inutil valoarea flag-ului = true, in metoda Worker nu mai exista nici-o verificare. Reproducerea aceastui comportament nu se realizeaza in debug mode, iar codul trebuie compilat cu target x86 si optimize on. Morala: corectitudinea unui program poate depinde de mai multi factori: versiunea de compilator, switch-urile de compilare, target-ul, versiunea de JIT si chiar tipul de CPU. Un alt exemplu interesant este tail recursion si JIT x64.
  9. 9. Thread-safety and volatile Aplicatia anterioara va functiona corect in toate situatiile daca marcam campul flag ca si volatile. Volatile poate fi aplicat field-urilor statice sau ale instantelor de urmatoarele tipuri: Byte, SByte, Int16, UInt16, Int32, UInt32, Char, Single, sau Boolean . Se poate aplica si campurilor de tip referinta si oricarui tip enum atata vreme cat suportul intern al campului enum este de tip Byte, SByte, Int16, UInt16, Int32, UInt32, Single, or Boolean . Compilatorul JIT ne asigura ca orice acces la campul marcat volatile se executa prin Citiri volatile Thread.VolatileRead iar scrierile prin Thread.VolatileWrite. De asemenea volatile indica compilatorului C# si JIT sa nu pastreze valoarea campului intr-un registru de memorie si sa il citeasca de fiecare data din memorie. CPU-urile de tip x86 x64 au cache coherency. Valoarea modificata de un thread care ruleaza pe CPU1 este propagata pe CPU2 si este vizibila imediat. ( Citirea si scrierea sunt atomice atat timp cat dimensiunea locatiei de memorie este 32 sau 64 biti in functie de arhitectura). Procesoarele Itanium cu au comunicatie directa intre CPU-uri si prin urmare volatile poate avea rolul de a face vizibile imediat modificarile dintr-un thread ruland pe CPU1 pentru un lat thread ruland pe CPU2.
  10. 10. Locking  ReaderWriterLock vs ReaderWriterLockSlim vs lock  Un readerwriterlock permite mai multe thread-uri sa obtina access la citire pentru o resursa si unui singur thread sa obtina acces de scriere la acea resursa. Deasemenea, permite unui thread care detine read lock se poate upgrada la writer fara sa elibereze read lock-ul detinut.  Gotcha: readerwriterlock este eficient atunci cand:  Exista mai multe thread-uri care citesc resursa decat thread-uri care scriu in resursa. Daca sunt multe scrieri, resursa va fi oricum blocata in majoritatea timpului iar cititorii vor trebui sa astepte.  Thread-urile care citesc resursa nu elibereaza foarte rapid read-lock-ul: daca blocul se executa foarte rapid este mai performant folosirea lock decat rwlock.
  11. 11. Locking  ReaderWriterLock vs ReaderWriterLockSlim vs lock  Un readerwriterlock permite mai multe thread-uri sa obtina access la citire pentru o resursa si unui singur thread sa obtina acces de scriere la acea resursa. Deasemenea, permite unui thread care detine read lock se poate upgrada la writer fara sa elibereze read lock-ul detinut.  Gotcha: readerwriterlock este eficient atunci cand:  Exista mai multe thread-uri care citesc resursa decat thread-uri care scriu in resursa. Daca sunt multe scrieri, resursa va fi oricum blocata in majoritatea timpului iar cititorii vor trebui sa astepte.  Thread-urile care citesc resursa nu elibereaza foarte rapid read-lock-ul: daca blocul se executa foarte rapid este mai performant folosirea lock decat rwlock.
  12. 12. ThreadLocal<T> vs ThreadStatic Campurile marcate cu ThreadStatic sunt initializate in constructorul static, care se executa o singura data. Valoarea campului va fi initializata doar pentru thread-ul sub care a fost rulat constructorul static. ThreadStatic nu este recomandat inaplicatii ASP.NET si WCF. Exista un moment in viata requestului in care infrastructura poate decide sa il opreasca temporar si apoi sa il ruleze pe un alt thread. ( in cazul unei incarcari mari). Pentru valorile care trebuie sa supravietuiasca doar pe durata unui request se recomanda folosirea HttpContext.Current.Items iar in cazul WCF: OperationContext. Atributul ThreadStatic nu poate fi aplicat decat campurilor statice.
  13. 13. ThreadLocal<T> vs ThreadStatic Un camp poate fi de tipul ThreadLocal<T> in ambele cazuri: static sau per instanta. Limitarile ThreadStatic (in contextul ASP.NET si WCF) se aplica si in cazul ThreadLocal<T> Constructorul poate primi un parametru factory de tip Func<T>, prin care ne putem asigura ca valoarea este initializata corect. Are proprietatile: IsValueCreated :bool si Value : T implementeaza IDisposable. Incepand cu .NET 4.5 exista posibilitatea sa obtinem lista completa de valori pentru toate thread-urile unde membrul ThreadLocal a fost initializat. Aceasta functionalitate este optionala, si este activata printr-un parametru suplimentar din constructor: trackingEnabled: bool.
  14. 14. dynamic Static typing – erorile sunt detectate la momentul compilarii, compilatorul genereraza cod compact si eficient. Dynamic typing – mai lent, poate fi convenabil in situatiile in care:  accesam structuri de date complexe care se mapeaza mai greu la o structura obiectuala  accesam obiecte COM care implementeaza IDispatch  accesam componente din alte limbaje care functioneza peste DLR: IronPython, IronRuby  dorim sa implementam multiple dispatch.C#3.5 Object wordapp=new Word.Application(); //create Word object Object fileName="MyDoc.docx"; //the specified Word document Object argu= System.Reflection.Missing.Value; Word.Document doc = wordapp.Documents.Open(ref fileName, ref argu,ref argu, ref argu, ref argu, ref argu, ref argu, ref argu,ref argu, ref argu, ref argu, ref argu, ref argu, ref argu, ref argu, ref argu);C#4 dynamic wordapp = new Word.Application(); dynamic doc = wordapp.Documents.Open(FileName: "MyDoc.docx");
  15. 15. dynamic – exemplu 1class DynamicXml : DynamicObject { public override bool TryGetMember(GetMemberBinder binder, out object result) { result = null; var attr = this.xml.Attributes().FirstOrDefault(x => x.Name.LocalName == binder.Name); if (attr == null) return false; result = attr.Value; return true; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { result = null; if (args.Length == 0) // intoarce un array de elemente { result = this.xml.Elements().Where(x=>x.Name.LocalName == binder.Name).Select(e => new DynamicXml(e)).ToArray(); return true; } if (args.Length == 1 && args[0] is int) // intoarce elementul pentru parametrul index { result = new DynamicXml(this.xml.Elements().ToArray()[(Int32)args[0]]); return true; } return false; } public override bool TryConvert(ConvertBinder binder, out object result) { result = null; if (binder.Type != typeof(string)) return false; result = this.xml.Value; return true; }
  16. 16. dynamic – exemplu 1 <?xml version="1.0" encoding="utf-8" ?>dynamic xml = new DynamicXml(XmlFiles.Sample1); <systems> <os>var firstName = (string) xml.os(0).name(0); <name>Linux</name>int count = xml.os().Length; <author>community</author> </os>foreach(var os in xml.os()){ <os> foreach(var name in os.name()) <name>Windows</name> { <author>Microsoft</author> Console.WriteLine((string)name); </os> } <os>} <name>MacOS</name> <author>Apple</author>Din nefericire dynamic nu poate fi folosit cu Linq. </os>In versiunea 4 exista un hack folosind metode de </systems>extensie, ce nu mai functioneaza in 4.5
  17. 17. dynamic – exemplu 1 <Project ToolsVersion="4.0" DefaultTargets="Build“ …> ...dynamic proj = new DynamicXml(XmlFiles.CsProjSample); <ItemGroup> <Reference Include="System" />foreach(var itemGroup in proj.ItemGroup()) <Reference Include="System.Core">{ <RequiredTargetFramework>3.5</RequiredTargetFramework> foreach (var reference in itemGroup.Reference()) </Reference> { <Reference Include="System.Xml.Linq"> Console.WriteLine((string) reference.Include); <RequiredTargetFramework>3.5</RequiredTargetFrame work> } </Reference>} <Reference nclude="System.Data.DataSetExtensions“/> <Reference Include="Microsoft.CSharp">Alte utilizari interesante: • accessarea fisierelor de configurare <RequiredTargetFramework>4.0</RequiredTargetFrame • private reflection work> • parsare sau creare json,xml,etc: </Reference> <Reference Include="System.Data" />dynamic ob = new ElasticObject(); <Reference Include="System.Xml" />ob.name = "Jeffrey"; </ItemGroup>ob.address.street = "Strada"; <ItemGroup>ob.favorites.book = "Inside Windows"; <Compile Include="Program.cs" />string json = ob > Format.Json; <Compile Include="PropertiesAssemblyInfo.cs" />
  18. 18. ThreadPool Exista un overhead legat de initializarea si pornirea unui thread: thread kernel object, alocare Thread environment block ( contine handlere de eroare + native thread local storage), alocare stack, notificare pentru toate dll-urile native din process DLL_THREAD_ATTACH => incarcarea in memorie a paginilor de cod care trateaza aceasta situtatie. Clr mentine un singur threadpool per proces. Intern se mentine o coada de task-uri pentru fiecare AppDomain si o coada separata pentru requesturi native (ASP.NET). Task-urile din lista sunt rulate in regim FIFO ( nu strict). Listele sunt protejate de printr-un monitor lock. Threadpool contine worker threads accesibile prin QueueUserWorkItem, si IO threads, folosite pentru notificarile operatiilor IO asincrone sau direct folosind UnsafeQueueNativeOverlapped.
  19. 19. ThreadPool net 3.5
  20. 20. ThreadPool algorithms Clr ajusteaza in mod automat numarul de thread-uri in functie de rata de finalizarea a task- urilor. Aceasta autoajustare este dificila datorita multitudinii de factori care afecteaza performanta threadpool care genereaza un “zgomot” ce face dificila corelarea intre intrari si iesiri. In NET 4.0 algoritmul a suferit o modificare bazata pe teoria procesarii semnalelor. S-a considerat numarul de thread-uri ca reprezentand un semnal de intare. Acest semnal este modificat intentionat de threadpool, urmarindu-se aparitia acestor fluctuatii in semnalul de iesire ( rata de executie a task-urilor) Acest algoritm performeaza optim pentru task-uri de durata medie 10ms, bine pentru task- uri de 250ms. Threadpool 4.0 folosete intern un ConcurrentQueue<T> care este lock-free si “prietenoasa” cu GC-ul. 10 milioane de task-uri empty Computer .net 3.5 .net 4.0 Imbunatatire Dual core 5 sec 2.45 sec 2x Quad core 19.5 sec 3.42 sec 6x
  21. 21. ThreadPool algorithms
  22. 22. ThreadPool algorithms
  23. 23. ThreadPool algorithms
  24. 24. Limitari ThreadPool Nu exista notificari de terminare a task-urilor . Nu exista un mecanism de comunicare a exceptiilor din task-uri catre initiatorul task-ului. Daca se stie faptul ca un task va avea o durata mare, nu exista modalitate de a indica threadpool acest lucru. O multitudine de task-uri de durata mare pot duce la blocarea majoritatii tread-urilor din pool. In multe cazuri exista o legatura intre task-urile programate pentru rulare asincrona. Daca un task A asteapta dupa un task B, iar task-ul B inca nu a fost programat pentru rulare, task-ul A blocheaza in mod inutil thread-ul asteptand dupa task-ul B. Nu exista posibilitatea de a anula (cancel) un task atunci cand a fost programat dar nu a fost inca executat si deasemenea nu exista un mecanism standard de semnalizarea a faptului ca dorim oprirea task-ului System.Timer.Timer isi executa callback-ul pe un thread din ThreadPool. Acuratetea acestor evenimente poate fi afectata de incarcarea curenta a thread-urilor din ThreadPool. Threadpool se acomodeaza relativ greu (0.5s) la cresterea brusca a volumului de task-uri care trebuie executate. Acest lucru este valabil atat pentru I/O threads cat si pentru worker threads.
  25. 25. Limitari ThreadPool (WCF) WCF executa request-urile folosind I/O threads si nu worker threads. In figura se evidentiaza modul in care TP creeaza noi pentru 40 de request-uri in rafala. Intra in actiune faptul ca TP creeaza un thread nou la 0.5 sec. Acest burst se configureaza folosind minIOThreads. Din pacate in .NET 3.5 exista un bug care face ca aceasta limita minima sa nu fie respectata decat pe o durata limitata. Bugfix exista in NET 4.0-4.5
  26. 26. Limitari ThreadPool (WCF) O solutie recomandata de Microsoft este extinderea WCF cu un custom operation behavior care sa ruleze requesturile WCF folosind worker threads. In cazul worker threads configurarea minWorkerThreads este respectata in toate cazurile.
  27. 27. Task Parallel Library (TPL) Reprezinta o colectie de clase ce ofera un API mai bogat pentru rularea de task-uri de granularitate fina optimizate pentru hardware modern (multi core). Versiunea CTP folosea un scheduler propriu. Modificarile aduse ThreadPool in NET 4.0 au permis folosirea threadpool ca scheduler default pentru TPL. TPL permite corelarea intre task-uri: ContinueWith, ContinueWhenAll, ContinueWhenAny. Deasemenea apelul Task.Wait gestioneaza situatia in mod inteligent: daca task-ul care este asteptat nu a fost programat inca, el va fi programat pentru rulare, in general inline, pe thread-ul curent, optimizandu-se utilizarea thread-rilor din pool. TPL permite utilizarea de scheduleri custom, pentru obtinerea unor pattern-uri de rulare complexe. TPL poate utilizeza SynchronizationContext pentru programarea task-urilor care modifica user interface-ul.( Windows Forms, WPF)
  28. 28. Imbunatatiri ThreadPool 4.0/Tasks In afara de lista globala de task-uri fiecare worker thread isi mentine o lista interna de tipul WSL ( work stealing queue ). Lista este lock free atunci cand este accesata privat si necesita sincronizare doar daca este accesata extern. Task-urile care sunt create in contextul executiei pe un worker thread sunt adaugate in lista locala a thread-ului. Task-uri create in contextul aplicatiei sunt adaugate in lista globala.
  29. 29. Imbunatatiri ThreadPool 4.0/Tasks
  30. 30. Imbunatatiri ThreadPool 4.0/Tasks
  31. 31. Imbunatatiri ThreadPool 4.0/Tasks Task-urile din lista locala sunt executate in regim LIFO. Probabilitatea ca datele referite de ultimul task sa se afle in L1, L2 cache este foarte mare si prin urmare rularea LIFO aduce un plus de performanta.
  32. 32. Imbunatatiri ThreadPool 4.0/Tasks In cazul in care un worker thread este idle, se verifica daca lista globala contine elemente. Daca lista globala este vida, se verifica listele locale ale celorlalte thread-uri. Daca se gaseste un element in asteptare este “furat” si alocat pe thread-ul idle. In cazul “furtului” de task-uri, lista locala vecina este consumata FIFO. Motivul este acela ca de obicei, in cazul algoritmilor recursivi de tip divide et impera, task-urile mai aproape de varf executa un numar mai mare de operatii.
  33. 33. Task, Task<T> Reprezinta o abstractizare la un nivel mai inalt decat acela de Thread. Un task poate fi sau nu rulat pe un thread dedicat. Task-urile pot fi inlantuite folosind continuations, iar executia acestui lant de task-uri poate fi conditionata in functie de terminarea cu success sau nu a task-ului precedent. Exista notiunea de child task Task-urile pot fi rulate pe threadpool, pe thread-ul de UI, sau folosind un custom scheduler. Task.Factory.StartNew(() => Console.WriteLine("Foo")); Crearea task-ului este “hot”, el este programat imediat pentru executie. Apelul Wait blocheza thread-ul curent pana la finalizarea task-ului (vezi Thread.Join ). var task = Task.Factory.StartNew(() => Thread.Sleep(1000)); Console.WriteLine(task.IsCompleted); task.Wait(); Putem indica TPL sa porneasca task-ul pe un thread separat si nu pe threadpool:Task.Factory.StartNew(() => Thread.Sleep(5000), TaskCreationOptions.LongRunning); Putem indica TPL sa incerce sa ruleze task-urile in ordinea in care au fost create:Task.Factory.StartNew(()=> Thread.Sleep(5000),TaskCreationOptions.PreferFairness);
  34. 34. Parent/Child Task Apelul Wait pentru parinte se finalizeaza atunci cand toate task-uri copii au fost deasemenea finalizate. Exceptiile din task-urile subordonate sunt ridicate sub forma unui AggregateException.var parent = Task.Factory.StartNew(() =>{ Console.WriteLine ("I am a parent"); Task.Factory.StartNew (() => // Detached task { Console.WriteLine ("I am detached"); }); Task.Factory.StartNew (() => // Child task { Console.WriteLine ("I am a child"); }, TaskCreationOptions.AttachedToParent);});parent.Wait();
  35. 35. Wait pentru mai multe task-uri var t1 = Task.Factory.StartNew(DoOperation1); var t2 = Task.Factory.StartNew(DoOperation2); Task.WaitAny(t1, t2); Task.WaitAll(t1, t2); WaitAll asteapta toate task-urile din lista, chiar daca unele s-au terminat cu exceptie. La final, WaitAll arunca o exceptie de tipul AggregateException. WaitAll, WaitAny, pot primi parametri suplimentari de tip TimeSpan sau CancellationToken. Cancelarea WaitAll nu opreste task-urile care sunt curent in executie. In cazul cancelarii se va ridica o AggregateException care poate contine mai multe TaskCancelledException.
  36. 36. Cancelarea task-urilor var cts = new CancellationTokenSource(); CancellationToken token = cts.Token; cts.CancelAfter(500); Task task = Task.Factory.StartNew(() => { try { Thread.Sleep(1000); token.ThrowIfCancellationRequested(); // Check for cancellation request } catch(OperationCanceledException) { throw; } }, token); try { task.Wait(); } catch (AggregateException ex) { Console.WriteLine(ex.InnerException is TaskCanceledException); // True Console.WriteLine(task.IsCanceled); // True Console.WriteLine(task.Status); // Canceled }
  37. 37. Continuations
  38. 38. Continuations ContinueWith executa un task dupa ce un alt task este finalizat. Un task se poate termina cu success, cu eroare sau poate fi cancelat. Task task1 = Task.Factory.StartNew(() => Console.Write("primul task...")); Task task2 = task1.ContinueWith(ant => Console.Write("..,continuation")); ContinueWith intoarce la randul sau un task, permitand inlantuirea mai multor task-uri. Un task si urmatorul continuation se pot executa pe thread-uri diferite. Putem forta continuation- ul sa se execute pe acelasi thread folosind flag-ul: TaskContinuationOptions.ExecuteSynchronously. Se poate obtine un plus de performanta evitandu-se delay-ul si un context switch suplimentar. Continuation si Task<T>. Task.Factory.StartNew<int>(() => 8) .ContinueWith(ant => ant.Result * 2) .ContinueWith(ant => Math.Sqrt(ant.Result)) .ContinueWith(ant => Console.WriteLine(ant.Result)); // 4 Exceptiile in task-ul precedent pot fi observate in continuation verificand proprietatile Exception, Result sau apeland Wait() si asteptand AggregateException. Un pattern safe este acela de propagare a exceptiei: Task.Factory.StartNew(() => { throw new Exception(); }) .ContinueWith(ant =>{ ant.Wait(); // Continuarea procesarii });
  39. 39. Continuations O alta modalitate de a trata exceptiile este aceea de a inlantui continuari diferite pentru stari diferite ale task-ului precedent: var task1 = Task.Factory.StartNew(() => { throw null; }); var error = task1.ContinueWith(ant => Console.Write(ant.Exception),TaskContinuationOptions.OnlyOnFaulted); var ok = task1.ContinueWith(ant => Console.Write("Success!"),TaskContinuationOptions.NotOnFaulted); Exemplue de metoda de extensie pentru observare si ignorare a exceptiei: public static void IgnoreExceptions (this Task task) { task.ContinueWith(t=>{var ignore=t.Exception;},askContinuationOptions.OnlyOnFaulted); } Task.Factory.StartNew (() => { throw null; }).IgnoreExceptions();
  40. 40. Gotcha continuations Daca un continuation nu se executa datorita optiunilor, nu este “ignorata” ci este considerata cancelled. Prin urmare, toate continuarile urmatoare vor fi executate, cu exceptia cazului in care au specificat flag-ul TaskContinuationOptions.NotOnCanceled Task t1 = Task.Factory.StartNew (...); Task fault = t1.ContinueWith (ant => Console.WriteLine ("fault"), TaskContinuationOptions.OnlyOnFaulted); Task t3 = fault.ContinueWith (ant => Console.WriteLine ("t3"));
  41. 41. Task si exceptiile Pornind de la comportamentul default in NET 4.0 prin care orice exceptie netratata intr-un thread duce la oprirea procesului, in NET 4.0 TPL a introdus notiunea de “observed Exceptions”. Orice task a carui exceptie nu este “observata” va duce la oprirea procesului. Aceasta verificare are loc la momentul colectarii task-ului in finalizer. Observarea exceptiei: Task.Wait sau accesarea proprietatii Exception sau IsFaulted intr-un continuation. Exista un handler global: TaskScheduler.UnobservedException. Acolo avem posibilitatea sa marcam exceptia ca observata. In .NET 4.5 task-urile au devenit un mecanism standard prin imbogatirea limbajului cu mecanismele async/await accesibile tuturor tipurilor de utilizatori. S-a renuntat la obligativitatea observarii exceptiilor. Handlerul TaskScheduler.UnobservedException va fi apelat totusi pentru fiecare eroare. Acest comportament este reconfigurabil la modul NET 4.0
  42. 42. Exemplu .NET 4.5 Task op1 = PerformOperation1(); Task op2 = PerformOperation2(); await op1; await op2; Daca op1 si op2 ridica impreuna exceptie, primul await va propaga exceptia catre codul apelant, pe cand al doilea nu va avea sansa sa fie observat si va duce mai tarziu la oprirea procesului. Comportamentul permisiv cu privire la exceptii din NET 4.5 poate fi schimbat:<configuration> <runtime> <ThrowUnobservedTaskExceptions enabled="true"/> </runtime></configuration> Se recomanda rularea testelor folosind acest flag setat true.
  43. 43. Async/Awaitprivate int longRunning(){ Thread.Sleep(1000); return 5;}private void button1_Click(object sender, EventArgs e){ this.button1.Enabled = false; Task.Factory.StartNew(() => longRunning()) .ContinueWith(t => { this.button1.Text = t.Result.ToString(CultureInfo.InvariantCulture); this.button1.Enabled = true; }, TaskScheduler.FromCurrentSynchronizationContext());}
  44. 44. Async/Await Metoda care va fi rulata asincron trebuie intoarca void, Task, sau Task<T>. Metoda apelanta care foloseste in interiorul ei apelul await trebuie marcata ca async. Compilatorul ar putea deduce automat acest pattern fara sa oblige programatorul sa marcheze metodele cu async. Await foloseste SynchronizationContext pentru a continua pe acelasi thread pe care metoda async a pornit. Rularea seriala datorata await este valabila doar in contextul metodei care contine apelul. private Task<int> longRunningAsync() { Thread.Sleep(1000); return Task.FromResult(5); } private async void button1_Click_Async(object sender, EventArgs e) { this.button1.Enabled = false; int i = await longRunningAsync(); this.button1.Text = i.ToString(CultureInfo.InvariantCulture); this.button1.Enabled = true; }
  45. 45. Async/Await
  46. 46. Async/Await Nu doar pentru obiecte de tipul Task se poate apela await. Se poate apela await pentru orice tip care detine o metoda cu numele GetAwaiter. Nu exista o interfata ce trebuie implentata, doar o metoda GetAwaiter ce intoarce un tip cu urmatoarea semnatura de metode: IsCompleted, OnCompleted(Action), GetResult(). Vestea buna este ca aceste metode pot fi metode de extensie. Putem extinde astfel orice tip ( in masura in care are sens) pentru a suporta pattern-ul await. Vom folosi chiar clasa awaiter pe care o intoarce Task.GetAwaiter(): using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; public static class AsyncExts { public static TaskAwaiter GetAwaiter(this TimeSpan span) { return Task.Delay(span).GetAwaiter(); } public static async void Demo() { await TimeSpan.FromMinutes(1); } }
  47. 47. Async/Await Await nu se poate aplica unui apel de metoda lista de task-uri. Folosind metode de extensie putem scrie: public static class AsyncExts2 { public static TaskAwaiter GetAwaiter(this IEnumerable<Task> tasks) { return Task.WhenAll(tasks).GetAwaiter(); } public static async void Demo() { var urls = new[] {"google.com", "msn.com"}; await (from url in urls select DownloadAsync(url)); } private static async Task<string> DownloadAsync(string url) { …. Code … } }
  48. 48. Async/Await Putem crea un awaiter pentru orice element care ofera notificari cu privire la finalizarea crearii sale sau la finalizarea unei actiuni. public static TaskAwaiter<int> GetAwaiter(this Process process) { var tcs = new TaskCompletionSource<int>(); if (process == null) { tcs.TrySetResult(-1); return tcs.Task.GetAwaiter(); } process.EnableRaisingEvents = true; process.Exited += (s, e) => tcs.TrySetResult(process.ExitCode); if (process.HasExited) tcs.TrySetResult(process.ExitCode); return tcs.Task.GetAwaiter(); } public static async void Demo() { await Process.Start("notepad.exe"); }
  49. 49. Async/Await Exemplu de awaiter custom: extindem await pentru controale windows forms. Codul care urmeaza apelului await va fi executat pe UI thread. public class WindowsFormsAwaiter { private readonly Control control; public WindowsFormsAwaiter(Control control) { this.control = control; } public bool IsCompleted { // daca avem invoke required suntem pe background thread si metoda noastra nu a rulat get { return !control.InvokeRequired; } } public void OnCompleted(Action continuation) { //ruleaza continuation pe ui thread control.BeginInvoke(continuation); } public void GetResult() { } }
  50. 50. Async/Await Exemplu de awaiter custom: extindem await pentru controale windows forms. Codul care urmeaza apelului await va fi executat pe UI thread. public static class AsyncExt4 { public static WindowsFormsAwaiter GetAwaiter(this Control control) { return new WindowsFormsAwaiter(control); } public static void Test(Label label1) { Task.Factory.StartNew(async () => { var text = GetTextFromLongRunningTask(); await (label1); label1.Text = text; //ruleaza pe ui thread }); }
  51. 51. Exceptii in Task.Wait vs await In cazul Task.Wait erorile sunt intotdeauna imbracate intr-o AggregateException. In .NET 4.5 mecanismul de propagare a exceptiilor a fost imbunatatit. A aparut clasa ExceptionDispatchInfo. Cu ajutorul ei putem capta o exceptie pe un thread si o putem ridica pe alt thread pastrandu-se intreaga informatie fara sa mai fie necesar sa o imbracam in alta exceptie. Exceptia e prezentata ca si cum flow-ul executiei ar fi trecut natural de pe thread-ul A in contextul thread-ului B cu prezervarea stack-ului si al Watson buckets. Await foloseste acest nou mecanism de ridicare al exceptiilor ridicand prima exceptie aparuta. Daca se doreste inspectarea tuturor exceptiilor aparute in urma rularii task-ului va trebui inspectata proprietatea Exception sau apelat GetResult(). Se va optine un AggregateException.
  52. 52. Async/Await Gotchas 1 Fie o functie WriteFileAsync Utilizare: In unele cazuri codul va functiona, in alte cazuri vor aparea exceptii sau rezultate imprevizibile in functia care urmeaza WriteFileAsync si depinde de rezultatul executiei acesteia.
  53. 53. Async/Await Gotchas 1 Functia WriteFileAsync trebui modificata astfel: Utilizare corecta
  54. 54. Async/Await Gotchas 2 Scrierea unui event handler async. Exemplu: in Windows 8 pentru implementarea data sharing intre aplicatii, trebuie tratat evenimentul DataTransferManager.DataRequested Folosind un handler ca cel de mai sus, aplicatia nu va functiona in regim de sharing. Solutia: indepartarea comportamentului asincron: async await.
  55. 55. Async/Await Gotchas 3 Tentatia de a apela metoda Wait() avand ca argument un Task returnat de o metoda async. Rezultatul poate fi un deadlock. public Result GetResult() { doWorkAsync().Wait(); //DEADLOCK! return CreateResult(); } private async Task doWorkAsync() { var task = Task.Delay(500); } Apelul doWorkAsync capteaza SynchronizationContext-ul curent. La finalizare va incerca sa intoarca controlul thread-ului de UI ( in Windows Forms, WPF ). Din pacate, thread-ul de UI este blocat asteptand finalizarea task-ului.
  56. 56. Parallel programming PFX In cazul computerelor de azi avem in mod obisnuit mai multe core-uri. Pentru a beneficia de intreaga putere de procesare trebuie sa parcurgem urmatorii pasi:  Partitionare optima a setului de date de intrare  Executarea in paralel a acestor seturi pe un numar de thread-uri ales cu grija in functie de core-urile disponibile  Agregarea resultatelor partiale pe fiecare thread in parte si in rezultatul final folosind structuri de date lock free
  57. 57. Parallel Linq (PLINQ) Plinq reprezinta cel mai facil mod de paralelizare a task-urilor. Se foloseste extensia AsParallel() si apoi body-ul query-ului linq ramane neschimbat. //calculez numerele prime mai mici decat 100000 var parallelQuery = from n in Enumerable.Range(3,1000000).AsParallel() where Enumerable.Range(2, (int)Math.Sqrt(n)).All(i => n % i > 0) select n; int[] primes = parallelQuery.ToArray(); Apelul AsParallel() intoarce un ParallelQuery<T>(), toti operatorii Linq ce urmeaza se refera la aceasta implementare. Pentru revenirea la rularea secventiala si la operatorii standard Linq se va apela extensia AsSequential(); In cazul iterarii resultatului unui linq query secvential obisnuit, rezultatele se obtin printr-o metoda pull dictata de client. In cazul unui query paralel, firele de executie calculeaza in avans o parte din rezultate pe care le stocheaza intr-un buffer intern, de unde le serveste client-ului care itereaza rezultatul. Daca clientul opreste iterarea, PLINQ opreste si el procesarea paralela pentru optimizarea consumului de resurse ( CPU/Memory). Buffering-ul intern vine in trei flavor-uri:  AutoBuffered  NotBuffered - util atunci cand dorim sa primim rezultatele cat mai repede posibil  FullyBuffered - folosit implict atunci cand apelam OrderBy, Reverse sau Aggregate
  58. 58. PLINQ si ordonarea Un efect colateral al paralelizarii executiei este acela ca rezultatul obtinut dupa reuniunea rezultatelor partiale nu mai respecta ordinea setului de intrare. Daca se doreste mentinerea ordinii initiale se foloseste AsOrdered(), daca nu mai este necesara mentinerea ordinii se poate reveni la comportamentul aleator, mai performant, folosind AsUnOrdered() inputSequence .AsParallel() .AsOrdered() // Mentine ordinea .QueryOperator1() .QueryOperator2() .AsUnordered() //De aici ordinea nu mai este mentinuta .QueryOperator3(); AsOrdered() este mai putin performant, AsUnordered() este comportamentul implicit.
  59. 59. PLINQ Cancellation var million = Enumerable.Range(3, 1000000); var cancelSource = new CancellationTokenSource(); var primeNumberQuery = from n in million.AsParallel() .WithCancellation(cancelSource.Token) where Enumerable.Range(2, (int) Math.Sqrt(n)).All(i => n%i > 0) select n; Task.Delay(1000) .ContinueWith(t => cancelSource.Cancel()); //cancel query after 1000 ms try { // Start query var primes = primeNumberQuery.ToArray(); } catch (OperationCanceledException) { Console.WriteLine ("Query canceled"); }
  60. 60. PLINQ Optimizari Unul din avantajele PLINQ este acela ca se ocupa de agregarea partitiilor in lista finala. De multe ori dorim doar executia unei procesari in paralel asupra tuturor elementelor. O optimizare o ofera metoda de extensie ForAll: "abcdef".AsParallel().Select(char.ToUpper).ForAll(Console.Write); Atunci cand intalneste apelul ForAll, PLINQ nu mai agrega partitiile in rezultatul final ci ruleaza procesarea direct asupra elementelor din partitii. Operatia de agregare poate deveni costisitoare atunci cand vorbim de un numar foarte mare de elemente.
  61. 61. PLINQ modalitati de partitionare Range partitioning  Functioneaza cu surse de date care implementeaza Ilist, IList<T>. Dau randament optim atunci cand prelucrarile pentru fiecare element au durate apropiate. Chunk partitioning  Este folosita in cazul unor surse de date non indexabile care implementeaza doar IEnumerable.  Este o partitionare dinamica, se incearca optimizare globala. Mai intai se aloca un numar mic de elemente in fiecare partitie, apoi pe masura ce apar noi elemente in sursa de date se dubleaza numarul de elemente alocat pe fiecare partitie. Se asigura partitionarea egala si atunci cand lista are un numar mic de elemente dar si cand numarul este foarte mare.  Schema poate fi avantajoasa si atunci cand prelucrarile asupra fiecarui element sursa au durate mult diferite. Daca un chunk a fost prelucrat complet, algoritmul aloca elementele disponibile din sursa, asigurandu-se o incarcare constanta. In cazul range partitioning, daca o partitie a fost prelucrata mai rapid, worker-ul ramane nefolosit. Striped partitioning  Este un caz particular al range partitioning folosit in cazul operatorilor TakeWhile, SkipWhile. Daca sunt doi workeri unul primeste elementele pare altul cele impare. Hashed partitioning  Cea mai costisitoare schema. Folosita in cazul operatorilor: Join, GroupJoin, GroupBy, Distinct, Except, Union, Intersect. Elementele sunt alocate in partitii pe baza hash-ului.
  62. 62. PLINQ LIMITARI  Functioneaza doar pentru provideri linq locali. Nu functioneaza pentru LinqToSql, EF sau alti provideri remote.  Daca se acceseaza alte variabile externe din query este necesara folosirea un or primitive de blocare (lock), lucru care poate aduce un impact performantei. Se recomanda structurile de date lock free. GOTCHAS  Limitarea apelului de metode thread-safe gen Console.WriteLine in interiorul PLINQ. Console.WriteLine foloseste un lock si forteaza un acces secvential, incetinind rularea query-urilor PLINQ  Paralelizarea excesiva, poate afecta performanta in mod advers: var q = from cust in customers.AsParallel() from order in cust.Orders.AsParallel() where order.OrderDate > date select new { cust, order };  Cust.Orders are un numar mare de elemente/ Operatia aplicata order este una complicata / Exista sufieciente core-uri.  Ordonari inutile  Atentie la accesul controalelor WPF, Windows Forms din PLINQ. ( thread affinity )  A nu se incerca sincronizarea manuala intre threadurile partitiilor sau iteratiilor.
  63. 63. Parallel.For si Parallel.Foreach Folosirea index-ului curentParallel.ForEach ("Hello, world", (c, state, i) =>{ Console.WriteLine (c.ToString() + i);}); Iesirea din bucla paralelaforeach (char c in "Hello, world") if (c == ,) break; else Console.Write (c);Parallel.ForEach ("Hello, world", (c, loopState) =>{ if (c == ,) loopState.Break(); else Console.Write (c);});
  64. 64. Parallel.For si Parallel.Foreach Oprirea completa Parallel.ForEach ("Hello, world", (c, loopState) => { if (c == ,) loopState.Stop(); else Console.Write (c); }); Cand pe alta partitie avem eroare sau a fost invocat Stop sau Break putem optimiza si opri iteratia curenta verificand: loopState.ShouldExitCurrentIteration Erorile sunt impachetate la final intr-un AggregateException.
  65. 65. Parallel.For si Parallel.Foreach Optimizarea concatenarii rezultatelor buclei folosind variabile locale pentru fiecare partitie (se evita folosirea primitivelor de locking) var locker = new object(); double grandTotal = 0; Parallel.For (1, 10000000, () => 0.0, // Initializare variabila locala per partitie (i, state, localTotal) => // Corpul functiei de procesare pe partitie. localTotal + Math.Sqrt (i), // returneaza noua valoare locala localTotal => // functia de concatenare a rezultatelor partitiilor { lock (locker) grandTotal += localTotal; } // aici trebuie folosita blocarea ); Avantajul este ca blocarea se realizeaza doar la concatenarea finala, nu si pentru fiecare iteratie.
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×