SlideShare ist ein Scribd-Unternehmen logo
1 von 65
Diverse C# 4.0
     2012
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);
         }
     }
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))
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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");
dynamic – exemplu 1
class 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;
         }
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
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" />
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.
ThreadPool net 3.5
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
ThreadPool algorithms
ThreadPool algorithms
ThreadPool algorithms
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.
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
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.
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)
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.
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 referite de ultimul task sa se afle in L1, L2 cache este foarte mare si
    prin urmare rularea LIFO aduce un plus de performanta.
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.
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);
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();
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.
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
 }
Continuations
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
                                });
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();
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"));
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
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.
Async/Await

private 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());
}
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;
    }
Async/Await
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);
        }
    }
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 …
        }
    }
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");
    }
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() { }
    }
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
                                         });
        }
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.
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.
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
    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.
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.
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
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
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.
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");
  }
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.
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.
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.
Parallel.For si Parallel.Foreach
      Folosirea index-ului curent
Parallel.ForEach ("Hello, world", (c, state, i) =>
{
      Console.WriteLine (c.ToString() + i);
});

      Iesirea din bucla paralela
foreach (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);
});
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.
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.

Weitere ähnliche Inhalte

Ähnlich wie Curs c#

curs porti logice si circuite digitale lectie curs 2
curs porti logice si circuite digitale lectie curs 2curs porti logice si circuite digitale lectie curs 2
curs porti logice si circuite digitale lectie curs 2dertify455
 
Javascript ajax tutorial
Javascript ajax tutorialJavascript ajax tutorial
Javascript ajax tutorialVlad Posea
 
Sisteme de Operare: Sincronizare
Sisteme de Operare: SincronizareSisteme de Operare: Sincronizare
Sisteme de Operare: SincronizareAlexandru Radovici
 
Fundamentele Limbajului Java
Fundamentele Limbajului JavaFundamentele Limbajului Java
Fundamentele Limbajului Javacolaru
 
Interactiunea cu Utilizatorul în Python, Java.pptx
Interactiunea cu Utilizatorul în Python, Java.pptxInteractiunea cu Utilizatorul în Python, Java.pptx
Interactiunea cu Utilizatorul în Python, Java.pptxrefewe9176
 
Procesare Rdf Sub .Net Framework
Procesare Rdf Sub .Net FrameworkProcesare Rdf Sub .Net Framework
Procesare Rdf Sub .Net Frameworkdodoshelu
 
Web 2016 (09/13) Procesarea datelor XML & HTML. Simple API for XML. Procesări...
Web 2016 (09/13) Procesarea datelor XML & HTML. Simple API for XML. Procesări...Web 2016 (09/13) Procesarea datelor XML & HTML. Simple API for XML. Procesări...
Web 2016 (09/13) Procesarea datelor XML & HTML. Simple API for XML. Procesări...Sabin Buraga
 
Dezvoltarea aplicaţiilor Web la nivel de client (cursul #9): JavaScript în na...
Dezvoltarea aplicaţiilor Web la nivel de client (cursul #9): JavaScript în na...Dezvoltarea aplicaţiilor Web la nivel de client (cursul #9): JavaScript în na...
Dezvoltarea aplicaţiilor Web la nivel de client (cursul #9): JavaScript în na...Sabin Buraga
 
Sisteme de Operare: Fire de executie
Sisteme de Operare: Fire de executieSisteme de Operare: Fire de executie
Sisteme de Operare: Fire de executieAlexandru Radovici
 

Ähnlich wie Curs c# (16)

curs porti logice si circuite digitale lectie curs 2
curs porti logice si circuite digitale lectie curs 2curs porti logice si circuite digitale lectie curs 2
curs porti logice si circuite digitale lectie curs 2
 
Javascript ajax tutorial
Javascript ajax tutorialJavascript ajax tutorial
Javascript ajax tutorial
 
Ruby EventMachine
Ruby EventMachineRuby EventMachine
Ruby EventMachine
 
Laborator 5
Laborator 5Laborator 5
Laborator 5
 
Curs java
Curs javaCurs java
Curs java
 
Sisteme de Operare: Sincronizare
Sisteme de Operare: SincronizareSisteme de Operare: Sincronizare
Sisteme de Operare: Sincronizare
 
Fundamentele Limbajului Java
Fundamentele Limbajului JavaFundamentele Limbajului Java
Fundamentele Limbajului Java
 
17 pointeri
17 pointeri17 pointeri
17 pointeri
 
17 pointeri
17 pointeri17 pointeri
17 pointeri
 
Tehnologii Java
Tehnologii JavaTehnologii Java
Tehnologii Java
 
Interactiunea cu Utilizatorul în Python, Java.pptx
Interactiunea cu Utilizatorul în Python, Java.pptxInteractiunea cu Utilizatorul în Python, Java.pptx
Interactiunea cu Utilizatorul în Python, Java.pptx
 
Procesare Rdf Sub .Net Framework
Procesare Rdf Sub .Net FrameworkProcesare Rdf Sub .Net Framework
Procesare Rdf Sub .Net Framework
 
Web 2016 (09/13) Procesarea datelor XML & HTML. Simple API for XML. Procesări...
Web 2016 (09/13) Procesarea datelor XML & HTML. Simple API for XML. Procesări...Web 2016 (09/13) Procesarea datelor XML & HTML. Simple API for XML. Procesări...
Web 2016 (09/13) Procesarea datelor XML & HTML. Simple API for XML. Procesări...
 
Dezvoltarea aplicaţiilor Web la nivel de client (cursul #9): JavaScript în na...
Dezvoltarea aplicaţiilor Web la nivel de client (cursul #9): JavaScript în na...Dezvoltarea aplicaţiilor Web la nivel de client (cursul #9): JavaScript în na...
Dezvoltarea aplicaţiilor Web la nivel de client (cursul #9): JavaScript în na...
 
Sisteme de Operare: Fire de executie
Sisteme de Operare: Fire de executieSisteme de Operare: Fire de executie
Sisteme de Operare: Fire de executie
 
6 protocoale de retea
6 protocoale de retea6 protocoale de retea
6 protocoale de retea
 

Curs c#

  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. dynamic – exemplu 1 class 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. 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. 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. 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.
  • 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
  • 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. 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. 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. 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. 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.
  • 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. 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. 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. 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. 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. 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 }
  • 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. 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. 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. 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. 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. Async/Await private 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. 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; }
  • 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. 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. 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. 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. 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. 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. 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. Async/Await Gotchas 1  Functia WriteFileAsync trebui modificata astfel:  Utilizare corecta
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. Parallel.For si Parallel.Foreach  Folosirea index-ului curent Parallel.ForEach ("Hello, world", (c, state, i) => { Console.WriteLine (c.ToString() + i); });  Iesirea din bucla paralela foreach (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. 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. 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.