Documente Academic
Documente Profesional
Documente Cultură
1/51
Fire in .NET
Documentatie: MSDN si Joseph Albahari - www.albahari.com/nutshell/
Cuprins Partea a-I-a Procese. AppDomain. Fire. Creare. Clasa Thread. Partea a-II-a Sincronizare. Obiecte de sincronizare. Sincronizare fire din cadrul unui proces. Sincronizare fire din procese diferite.
Asiminoaei Ioan
Unul sau mai multe fire managed (create in .NET) reprezentate de System.Threading.Thread, pot rula in unul sau mai multe AppDomain din cadrul aceluaisi proces. Fiecare AppDomain este startat cu un singur fir ; codul din acest AppDomain poate crea alte AppDomain si fire. Un fir este unitatea de baza la care SO aloca timp procesor. Un fir poate executa orice parte de cod a unui proces, incluzand parti curente executate de alt fir. Firul are propria stiva, deci variabilele locale sunt gestionate separat. Un thread pool este o colectie de fire de lucru care executa asincron metode callback. Thread pool furnizeaza un management al firelor de lucru. C# suporta executia paralela a codului prin multithreading. Un program client C# (Console, WPF sau Windows Forms) este startat intr-un singur fir creat automat de CLR si de SO (fir principal, fir primar) si devine multifir prin crearea de fire aditionale. Un fir isi termina executia cand delegate-ul pasat in ctor Thread isi termina executia. O data terminat un fir nu poate fi restartat. Putem verifica daca un fir este in executie folosind proprietatea IsAlive. Spatiul de nume pentru fire este System.Threading. Observatie Firele partajeaza date daca au o referinta comuna la aceeasi instanta a obiectului. Variabilele locale sunt pe stiva. Campurile statice sunt partajate intre fire.
Asiminoaei Ioan
namespace Fire_2011 { class MainFire_1 { // Variabila done este folosita in metoda Go pe // aceeasi instanta a clasei MainFire_1: mf // Prima data in firul creat, gestionat de metoda Go // si a doua oara in apelul metodei Go din firul principal bool done; // Metoda a instantei. Va fi folosita in firul principal si de // asemenea pentru a crea un fir. // Foloseste variabilele locale text si i void Go() { // Variabila tip referinta, este locala in cadrul firului string text = "Iasi"; // variabila tip valoare, e locala => pe stiva int i = 0; if (i == 0) Console.WriteLine("Nume fir = '{0}' ; i = {1}, done = {2}, + text = {3}", Thread.CurrentThread.Name, i, done, text); // Firul ce apeleaza aceasta metoda va fi starea Sleep // pentru 10 milisecunde. Thread.Sleep(10); // simulare activitate while (i < 10000) i++; Console.WriteLine("Nume fir = '{0}' ; i = {1}, done = {2}, + text = {3}", Thread.CurrentThread.Name,i, done, text); if (!done) { done = true; text = Thread.CurrentThread.Name; Console.WriteLine("Done. Nume fir : '{0}', done = {1}, + text = {2}", Thread.CurrentThread.Name, done, text); } } static void Main(string[] args) { Thread.CurrentThread.Name = "Fir principal";
Asiminoaei Ioan
In exemplele ce urmeaza se considera ca am importat spatiile de nume System si System.Threading. Exemplu cu un camp static definit in clasa ce contine metoda firului (metoda a instantei).
class Main2 { // data membru statica, partajata static bool sdone; static void Main(string[] args) { Main2 m2 = new Main2(); Thread.CurrentThread.Name = "Fir principal"; // Creare fir si lansare Thread f = new Thread(m2.GoS); f.Name = "Fir 2"; f.Start(); // apel metoda GoS din firul principal m2.GoS(); } void GoS() { if (!sdone) { Console.WriteLine("GoS => {0}",
Asiminoaei Ioan
Ne-am astepta ca acest mesaj sa apara o singura data. Ce se intampla? In acest cod simplu e usor de explicat. Ce ne facem intr-un cod complex? Daca in metoda GoS schimbam ordinea instructiunilor din if astfel incat
sdone = true ;
sa fie inainte de Console.WriteLine(...) rezultatul este altul : GoS apare scris o singura data. Observatie Intr-un mediu multifir accesul la datele (resurse) partajate trebuie protejat. Explicatie Firul primar creaza si lanseaza Fir 2 care va fi luat in evidenta de procesul de planificare al firelor. Este lansat acest fir si apoi pus in asteptare (Thread.Sleep(10)) 10 milisecunde. Un fir blocat nu consuma resurse CPU. Este trecut in executie firul principal care se va opri si el in acelasi loc. A expirat timpul de asteptare pentru Fir 2 si isi reia executia dupa care se termina. In continuare isi reia executia firul principal si apoi se termina si acesta. Variabila sdone a fost actualizata cu aceeasi valoare de doua ori. Observatie Problema se rezolva folosind un obiect pentru sincronizare fire in cadrul metodei GoS. Fiind fire din cadrul aceluaisi proces putem folosi un lock sau Monitor (e sectiunea critica din Win32 API), care reprezinta acelasi lucru. Se protejeaza sectiunea de cod ce modifica resursa partajata. Codul ce este protejat astfel impotriva nedeterminarii intr-un context multifir se numeste threadsafe.
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 6/51 Codul de mai sus poate fi modificat astfel (modificarile sunt cele scrise cu rosu) pentru a proteja sectiunea critica de cod:
class Main2 { static bool sdone; // Atentie: unde se declara obiectul folosit in blocare private object criticalSection = new object(); static void Main(string[] args) { Main2 m2 = new Main2(); Thread.CurrentThread.Name = "Fir principal"; Thread f = new Thread(m2.GoS); f.Name = "Fir 2"; f.Start(); m2.GoS(); } void GoS() { lock(criticalSection) { if (!sdone) { Console.WriteLine("GoS => {0}", Thread.CurrentThread.Name); Thread.Sleep(10); sdone = true; } } } }
O alta solutie poate fi aceea de a astepta in firul principal terminarea firului creat ( Fir 2 ) scriind urmatoarea linie de cod inainte de m2.GoS() si dupa f.Start();
f.Join();
si nu mai avem nevoie de lock si nici de variabila criticalSection. Observatie Thread.Sleep(0) sau Thread.Yield() dau o sansa si altor fire de a fi alocate pe procesor. Thread.Yield() se refera la firele ce ruleaza pe acelasi procesor.
Asiminoaei Ioan
Thread(ThreadStart, Int32)
Description Initializes a new instance of the Thread class, specifying a delegate that allows an object to be passed to the thread when the thread is started. Initializes a new instance of the Thread class. Initializes a new instance of the Thread class, specifying a delegate that allows an object to be passed to the thread when the thread is started and specifying the maximum stack size for the thread. Initializes a new instance of the Thread class, specifying the maximum stack size for the thread.
Observatie In acest caz metoda pentru fir are prototipul void MetodaFir(), adica returneaza void si nu are parametri. Lansarea in executie a firului creat se face cu metoda Start().
Exemplu: class ThreadTest { static void Main() { Thread t = new Thread (new ThreadStart (Go)); // Lansare in executie metoda Go() pe noul fir. t.Start(); // Apel metoda Go() in firul principal. Go(); } static void Go() { Console.WriteLine ("Hello!"); }
Asiminoaei Ioan
Asiminoaei Ioan
sau
new Thread (() => { Console.WriteLine ("I'm running on another thread!"); Console.WriteLine ("This is so easy!"); }).Start();
Metode anonime
new Thread (delegate() { ... }).Start();
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 10/51 Al doilea ctor accepta un argument de tip object. Trebuie sa facem cast la tipul asteptat in metoda firului. Observatie Cand folosim expresii lambda variabilele trebuie sa fie locale, in caz contrar rezultatul este nedeterminat. Exemplu
for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start();
Problema este ca variabila i refera aceeasi locatie in timpul executiei buclei for. Rezolvarea consta in a defini o variabila temporara in bucla for.
for (int i = 0; i < 10; i++) { int temp = i; new Thread (() => Console.Write (temp)).Start(); }
Asiminoaei Ioan
Prioritatea firului
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }
Observatie Prioritatea firului este strans legata de clasa de prioritate a procesului. In .NET prioritatea procesului este descrisa astfel :
public enum ProcessPriorityClass { Idle, Normal, High, AboveNormal, BelowNormal, RealTime};
In Win32 API pentru clasele de prioritate ale unui proces au fost definite urmatoarele: (Clasele de prioritate ale proceselor din tabel lipsesc BelowNormal si AboveNormal) Clasa de prioritate Descriere IDLE_PRIORITY_CLASS Procesul ruleaz doar cnd sistemul este liber, de exemplu cnd nici un alt fie de execuie nu ateapt s fie executat. NORMAL_PRIORITY_CLASS Clasa de prioritate implicit. Procesul nu are nevoi speciale de planificare. HIGH_PRIORITY_CLASS Procesul primete o prioritate mai mare ca un proces IDLE_PRIORITY_CLASS sau NORMAL_PRIORITY_CLASS. REALTIME_PRIORITY_CLASS Procesul are cea mai mare prioritate posibil, mai ridicat chiar dect HIGH_PRIORITY_CLASS.
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 12/51 Legatura dintre clasa de prioritate a unui proces si prioritatea firului este data in urmatorul tabel (in SO Vista, Windows 7, Windows 8 e posibil ca aceste valori sa fie modificate): Clasa de prioritate a procesului Prioritatea reltiv a firului Idle Below Normal Above Normal Normal Time-critical 15 15 15 15 Highest 6 8 10 12 Above normal 5 7 9 11 Normal 4 6 8 10 Below normal 3 5 7 9 Lowest 2 4 6 8 Idle 1 1 1 1
High RealTime 15 31 15 26 14 25 13 24 12 23 11 22 1 16
Tratare exceptii
Observatie Exceptiile vor fi tratate in metoda firului si nu in secventa de creare si lansare a firului. Exemplu eronat
public static void Main() { try { new Thread (Go).Start(); } catch (Exception ex) { // Nu se ajunge aici nicodata Console.WriteLine ("Exception!"); } }
Asiminoaei Ioan
Thread Pooling
Contine fire preconstruite, care pot fi lansate mai repede de catre SO. Pattern-ul metodelor asincrone foloseste thread pool. Pentru a folosi thread pool .NET pune la dispozitie urmatoarele facilitati: TPL - Task Parallel Library (din Framework 4.0) Apel ThreadPool.QueueUserWorkItem Delegates asincroni
BackgroundWorker
Constructii indirecte ce folosesc thread pool: WCF Windows Communication Foundation, Remoting, ASP.NET, si ASMX Web Services servere de aplicatii System.Timers.Timer si System.Threading.Timer Metode din framework .NET care se termina in Async, cum ar fi cele din WebClient, si cele mai multe metode de tipul BeginXXX. PLINQ. Observatie Nu putem seta numele pentru un fir din thread pool. Firele din thread pool sunt numai background. Proprietatea Thread.CurrentThread.IsThreadPoolThread poate fi folosita pentru a determina daca suntem intr-un fir din thread pool.
Lansare fir din Thread Pool folosind TPL Task Parallel Library
Clasa Task
(.NET framework 4.0) Consultati MSDN.
Task lucreaza bine pe masini cu procesoare cu nuclee multiple. Exista in doua variante: non-
Asiminoaei Ioan
terminarea executiei. Clasa generica Task<Result> este o subclasa a clasei nongenerice Task. Aceasta ne permite sa preluam valoarea returnata de task dupa terminarea executiei.
Exemplu: static void Main() { // Lansare in executie metoda DownloadString Task<string> task = Task.Factory.StartNew<string> ( () => DownloadString ("http://www.linqpad.net") ); // Cod ce se va executa in paralel cu DownloadString RunSomeOtherMethod(); // Preluare rezultat. Daca metoda este inca in executie // firul curent se va bloca (intra in asteptare) pana // cand se termina DownloadString string result = task.Result; } static string DownloadString (string uri) { using (var wc = new System.Net.WebClient()) return wc.DownloadString (uri); }
Asiminoaei Ioan
Introducere in TPL
Spatiul de nume System.Threading.Tasks. Clasele Task, Task<TResult>,TaskFactory<TResult> si Parallel. Task Parallel Library (TPL) este bazata pe conceptul de task. Termenul task parallelism se refera la unul sau mai multe task-uri ce ruleaza in mod concurent. Un task reprezinta o operatie asincrona si se aseamana cu crearea unui nou fir sau a unui fir de lucru din ThreadPool, dar furnizeaza urmatoarele posibilitati: Utilizarea resurselor sistem mai eficient si mai scalabil. Task-urile sunt puse intr-o coada la ThreadPool, care gestioneaza eficient aceste task-uri avand algoritmi pentru load balancing. Mai mult control asupra firelor. Task si framework-ul construit pentru acestea furnizeaza un API ce suporta asteptarea, intreruperea si continuarea executiei precum si tratarea execeptiilor, detalii de stare, planificare customizata.
Clasa Parallel
Furnizeaza suport pentru executia buclelor in paralel. Forma de baza a paralelismului este structurata prin urmatoarele metode statice din clasa Parallel (metodele sunt blocante): Metoda folosita este Parallel.Invoke ce accepta ca parametru un delegate Action.
Parallel.Invoke: Executa un tablou de delegates in paralel si apoi asteapta terminarea
acestora.
Parallel.For : Echivalentul paralel al buclei for din C#. Parallel.ForEach: Echivalentul paralel al buclei foreach din C#.
Parallel.Invoke
public static void Invoke (params Action[] actions); Invoke( Action []) Executa fiecare actiune, posibil in paralel. Invoke(ParallelOptions, Action []) - Executa fiecare actiune, posibil in paralel,
Asiminoaei Ioan
Exemplu
Parallel.Invoke ( () => new WebClient().DownloadFile ( "http://www.ipotesti.net", "Lacul.html"), () => new WebClient().DownloadFile ("http://www.ozana.net", "La scaldat.html"));
Parallel.For si Parallel.ForEach
Parallel.For si Parallel.ForEach sunt echivalente cu for si foreach din C# , dar
Asiminoaei Ioan
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 18/51 Exemplu MSDN (modificat metoda CreateWordArray si primul task din Invoke):
using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Threading; System.Threading.Tasks;
namespace PaskParalelism { class ParallelInvoke { static void Main() { // Creare array de string string[] words = CreateWordArray(); string longestWord = ""; #region ParallelTasks // Executa trei task-uri in paralel pe sursa words Parallel.Invoke( () => { Console.WriteLine("Incepe primul task..."); longestWord = GetLongestWord(words); }, // close first Action () => { Console.WriteLine("Incepe al doilea task..."); GetMostCommonWords(words); }, //close second Action () => { Console.WriteLine("Incepe al treilea task..."); GetCountForWord(words, "Iasi"); } //close third Action ); //close Parallel.Invoke Console.WriteLine("Returned from Parallel.Invoke"); #endregion Console.WriteLine("Press any key to exit"); Console.WriteLine("Fir principal: longestWord = {0}", longestWord); Console.ReadKey(); } #region HelperMethods private static void GetCountForWord(string[] words, string term) { var findWord = from word in words where
Asiminoaei Ioan
Asiminoaei Ioan
Clasa Task
Instantele clasei Task pot fi create: Folosind proprietatea Factory ce returneaza o instanta pentru TaskFactory si in continuare metoda SartNew. Folosind un ctor al clasei Task si in continuare apelul metodei Start. Cand cream un task ii furnizam un delegate ce contine codul ce se va executa. Delegate-ul poate fi furnizat ca un delegate cu nume, metoda anonima sau o expresie lambda. Expresiile lamba pot contine apel la metode cu nume (vezi si exemplul anterior).
Exemplu:
sau
// Un array de Task ce returneaza double // Metodele folosite sunt Do1, Do2, Do3
Task<double>[] taskArray = new Task<double>[] { Task<double>.Factory.StartNew(() => Do1()), // May be written more conveniently like this: Task.Factory.StartNew(() => Do2()), Task.Factory.StartNew(() => Do3()) }; // Preluare rezultate double[] results = new double[taskArray.Length]; for (int i = 0; i < taskArray.Length; i++) results[i] = taskArray[i].Result;
Asiminoaei Ioan
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 22/51 sau (atentie la cuvantul cheie dynamic) :
class MyCustomData { public DateTime Date; public String Name; }
static void TaskDemo2() { // Create the task object by using an Action<object> to // pass in custom data. var taskB = Task.Factory.StartNew( (obj) => { MyCustomData data = (MyCustomData)obj; Console.WriteLine("Hello from {0}. Today is {1}.", data.Name, data.Date); }, new MyCustomData { Name = "taskB", Date = DateTime.Today } ); // Nu e nevoie sa cream o clasa cu nume pentru a // transmite datele : folosim cuvantul cheie dynamic var taskC = Task.Factory.StartNew( (state) => { // atentie la cuvantul cheie dynamic dynamic data = state; Console.WriteLine("TaskC : name : {0}, Date {1}, Time = {2}", data.Name, data.Date, data.Time); }, // tip anonim de unde preia datele new { Name = "taskC", Date = DateTime.Today, Time = 10 } ); }
urmat de apel TestDemo2() in metoda Main. Observatie: In .NET 4.5 apare metoda Run in clasa Task.
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 23/51 Constructori pentru clasa Task (partial) MSDN
Name Task(Action) Task(Action, CancellationToken) Task(Action, TaskCreationOptions) Task(Action <Object >, Object) Task(Action, CancellationToken, TaskCreationOptions) Task(Action <Object >, Object, CancellationToken) Task(Action <Object >, Object, TaskCreationOptions) Task(Action <Object >, Object, CancellationToken, TaskCreationOptions)
Description Initializes a new Task with the specified action. Initializes a new Task with the specified action and CancellationToken while observing a cancellation token. Initializes a new Task with the specified action and creation options. Initializes a new Task with the specified action and state. Initializes a new Task with the specified action and creation options. Initializes a new Task with the specified action, state, and options. Initializes a new Task with the specified action, state, and options. Initializes a new Task with the specified action, state, and options.
Anularea : Cancellation
Posibilitatea de a anula executia unui task. In continuare exemplificam cateva metode de anulare a executiei. Tipurile CancellationTokenSource si CancellationToken. Ideea este de a pasa in ctor un obiect ce va da posibilitatea anularii executiei task-ului creat. Obiectul ce invoca o operatie ce poate fi anulata de exemplu prin crearea unui nou fir sau task, transmite acest token operatiei. Operatia invocata poate transfera acest obiect mai departe la alte operatii. Obiectul ce a creat acest token privitor la anularea unei operatii poate interoga mai tarziu operatia pentru a vedea ce s-a intamplat.
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 24/51 Acest model de anulare al operatiilor are urmatoarele caracteristici: Un obiect ce invoca o operatie anulabila poate controla modul cand s-a cerut anularea operatiei. In momentul cand s-a cerut anularea operatiei, task-ul poate decide terminarea acesteia intr-un mod sigur (salvari de siguranta, eliberare resurse, etc.). Un listener poate asculta pe multiple tokens-uri in mod simultan. Listeners-ii pot fi notificati de cererea de anulare prin : inregistrari callback sau asteptari pe anumite evenimente. Anularea se refera la operatii, nu la obiecte. Cererea de anulare inseamna ca operatia ar trebui sa se opreasca cat de repede posibil dupa ce a realizat eliberarea resurselor. Un token de anulare se refera la o singura operatie de anulare, operatie ce trebuie implementata in program. Dupa ce proprietatea IsCancellationRequested a fost setata pe true (s-a apelat metoda Cancel() pe token de anulare), aceasta nu mai poate fi resetata, deci acest obiect de anulare nu poate fi refolosit dupa ce a fost anulat . Daca avem nevoie de un mecanism de anulare bazat pe un obiect vom inregistra pentru fiecare obiect un token de anulare. Exemplu: cancel la nivel de operatie si cancel la nivel de obiect
class CancelObject { public CancelObject() { Console.WriteLine("CancelObject ctor"); } public void Cancel() { Console.WriteLine("Metoda Cancel a fost apelata!"); } } static void Main(string[] args) { // Cancel la nivel de operatie CancellationTokenSource ctstop = new CancellationTokenSource(); ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), ctstop.Token); // cerere pentru cancel // Se doreste aprirea executiei metodei DoWork. ctstop.Cancel();
Asiminoaei Ioan
Asiminoaei Ioan
Clasa Task<TResult>
Reprezinta o operatie asincrona ce returneaza o valoare. Detalii complete in MSDN. Constructori
Name Task <TResult >(Func <TResult >) Task <TResult >(Func <TResult >, CancellationToken) Task <TResult >(Func <TResult >, TaskCreationOptions) Task <TResult >( Func <Object, TResult >, Object) Task <TResult >(Func <TResult >, CancellationToken, TaskCreationOptions) Task <TResult >( Func <Object, TResult >, Object, CancellationToken) Task <TResult >( Func <Object, TResult >, Object, TaskCreationOptions) Task <TResult >( Func <Object, TResult >, Object, CancellationToken, TaskCreationOptions)
Description Initializes a new Task <TResult > with the specified function. Initializes a new Task <TResult > with the specified function. Initializes a new Task <TResult > with the specified function and creation options. Initializes a new Task <TResult > with the specified function and state. Initializes a new Task <TResult > with the specified function and creation options. Initializes a new Task <TResult > with the specified action, state, and options. Initializes a new Task <TResult > with the specified action, state, and options. Initializes a new Task <TResult > with the specified action, state, and options.
Metode Description Creates a continuation that executes when the target Task ContinueWith(Action <Task >) completes. (Inherited from Task.) Runs the Task synchronously on the current TaskScheduler. RunSynchronously () (Inherited from Task.) Runs the Task synchronously on the TaskScheduler RunSynchronously(TaskScheduler) provided. (Inherited from Task.) Starts the Task, scheduling it for execution to the current Start () TaskScheduler. (Inherited from Task.) Start(TaskScheduler) Starts the Task, scheduling it for execution to the specified
Name
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 27/51 TaskScheduler. (Inherited from Task.) Waits for the Task to complete execution. (Inherited from Wait () Task.) Waits for the Task to complete execution. (Inherited from Wait(CancellationToken) Task.) Waits for the Task to complete execution. (Inherited from Wait(Int32) Task.) Waits for the Task to complete execution. (Inherited from Wait(TimeSpan) Task.) Waits for the Task to complete execution. (Inherited from Wait(Int32, CancellationToken) Task.) Observatie Atentie la prototipul metodei Wait(CancellationToken). Posibilitatea de a evita deadlock. Proprietati
Name AsyncState Factory Id IsCanceled IsCompleted IsFaulted Result Status
Description Gets the state object supplied when the Task was created, or null if none was supplied. (Inherited from Task.) Provides access to factory methods for creating Task <TResult > instances. Gets a unique ID for this Task instance. (Inherited from Task.) Gets whether this Task instance has completed execution due to being canceled. (Inherited from Task.) Gets whether this Task has completed. (Inherited from Task.) Gets whether the Task completed due to an unhandled exception. (Inherited from Task.) Gets the result value of this Task <TResult >. Gets the TaskStatus of this Task. (Inherited from Task.)
Asiminoaei Ioan
Proprietatea Factory
Furnizeaza acces la metodele pentru crearea instantelor Task<TResult>. Returneaza o instanta a clasei TaskFactory<TResult>. Ctor pentru TaskFactory<TResult> Description Initializes a TaskFactory <TResult > TaskFactory <TResult>() instance with the default configuration. TaskFactory <TResult> Initializes a TaskFactory <TResult > (CancellationToken) instance with the default configuration. Initializes a TaskFactory <TResult > TaskFactory <TResult> (TaskScheduler) instance with the specified configuration.
TaskFactory <TResult> (TaskCreationOptions, TaskContinuationOptions) TaskFactory <TResult> (CancellationToken, TaskCreationOptions, TaskContinuationOptions, TaskScheduler) Name
Initializes a TaskFactory <TResult > instance with the specified configuration. Initializes a TaskFactory <TResult > instance with the specified configuration.
Asiminoaei Ioan
Description Queues a method for execution. The method executes when a thread pool thread becomes available. Queues a method for execution, and specifies an object containing data to be used by the method. The method executes when a thread pool thread becomes available.
Exemplu static void Main() { ThreadPool.QueueUserWorkItem (Go); ThreadPool.QueueUserWorkItem (Go, 123); Console.ReadLine(); } static void Go (object data) // data will be null with the first call. { Console.WriteLine ("Hello from the thread pool! " + data); } Rezultat: Hello from the thread pool! Hello from the thread pool! 123
Observatie In metoda Go trebuie sa facem cast la tipul dorit. Nu putem obtine rezultatul executiei metodei Go. Exceptiile trebuiesc tratate in metoda Go.
Delegates asincroni vezi cursul de la delagates.
Asiminoaei Ioan
Sincronizare
Constructiile pentru sincronizare pot fi impartite in urmatoarele categorii: Metode simple de blocare
Sleep Join Task.Wait
Asteapta ca un fir sa-si termine executia sau sa nu lucreze o perioada de timp. Constructii cu blocari Blocari exclusive: permit accesarea de date comune fara a cauza interferente nedorite. Aceste constructii se realizeaza cu:
Monitor.Enter/Monitor.Exit Mutex SpinLock.
Blocari neexclusive:
Semaphore SemaphoreSlim blocari read/write (the reader/writer locks). Clasa ReaderWriterLock.
Constructii bazate pe semnalizare Permit ca un fir sa astepte pana primeste o notificare de la alt fir. Cele mai des utilizate sunt:
EventWaitHandle (clasele AutoResetEvent, ManualResetEvent) metodele Wait si Pulse.
Constructii de sincronizare neblocante Acestea protejeaza accesul la un camp comun. CLR si C# furnizeaza urmatoarele constructii neblocante: Asiminoaei Ioan
Blocarea
Un fir este blocat cand executia sa este trecuta in starea de pauza, aceasta inseamna Sleep sau asteptare pe un alt fir via Join sau EndInvoke. Un fir in aceasta stare nu consuma timp processor. Putem testa daca un fir este blocat astfel:
bool blocked = (someThread.ThreadState & ThreadState.WaitSleepJoin) != 0;
Cand un fir trece din starea blocat in starea neblocat sau invers, sistemul de operare realizeaza un context switch (de ordinal microsecundelor), trecerea dintr-o stare in alta.
Actiunea de blocare
Constructii cu lock si Mutex. Observatie: lock lucreaza cu tipuri referinta. Exemplu obiectul folosit pentru blocare este _locker:
class ThreadSafe { static readonly object _locker = new object(); static int _val1, _val2; static void Go() { lock (_locker) { if (_val2 != 0) Console.WriteLine (_val1 / _val2); _val2 = 0; } } }
Actiunea de blocare exclusiva este folosita pentru a asigura faptul ca numai un fir executa o sectiune de cod la un moment dat. lock folosit in fire din cadrul aceluiasi process. Mutex folosit in fire din cadrul aceluaisi process sau din procese diferite, in cazul din urma mutexul are un nume.
Asiminoaei Ioan
Monitor.Enter - Monitor.Exit.
Monitor.TryEnter
Instructiunea lock este sinonima cu metodele Monitor.Enter si Monitor.Exit, cu un bloc try/finally. Exemplul de mai sus poate fi:
Monitor.Enter (_locker); // cod // aici ar putea aparea o exceptie try { if (_val2 != 0) Console.WriteLine (_val1 / _val2); _val2 = 0; } catch(...) { // cod } finally { Monitor.Exit (_locker); }
Observatie Daca intre Monitor.Enter si blocul try apare o exceptie este posibil ca Monitor.Enter sa se fi executat si atunci blocul finally nu se mai executa, deci nu avem eliberare resursa. Pentru a preveni acest lucru, in .NET 4.0 s-a adaugat:
public static void Enter (object obj, ref bool lockTaken);
unde al doilea parametru va avea valoarea true daca Monitor.Enter s-a executat. Codul de mai sus poate fi scris (de observat ca Monitor.Enter este in blocul try in acest caz) :
bool lockTaken = false; try { Monitor.Enter (_locker, ref lockTaken); // cod } catch(...) { // cod } finally { if (lockTaken) Monitor.Exit (_locker); }
Alegerea obiectului de sincronizare: obiectul trebuie sa fie tip referinta si sa fie vizibil tuturor firelor.
Asiminoaei Ioan
Deadlocks
Exemplu
object locker1 = new object(); object locker2 = new object(); new Thread (() => { lock (locker1) { Thread.Sleep (1000); lock (locker2); // Deadlock } }).Start(); // cod in firul parinte : cel ce a lansat firul de mai sus lock (locker2) { Thread.Sleep (1000); lock (locker1); // Deadlock }
Asiminoaei Ioan
// // // //
Asiminoaei Ioan
Mutex
Mutex este asemanator cu lock, dar poate sincroniza fire din cadrul aceluiasi process sau din procese diferite. Pentru un Mutex SO ii ataseaza un proprietar. Clasa Mutex Constructori
Name Mutex() Mutex(Bolean)
Description
Initializes a new instance of the Mutex class with default properties.
Initializes a new instance of the Mutex class with a Boolean value that indi whether the calling thread should have initial ownership of the mutex. Initializes a new instance of the Mutex class with a Boolean value that indicates whether the calling thread should have initial ownership of the mutex, and a string that is the name of the mutex.
Mutex(Boolean, String)
Metode
Name Close()
Description
When overridden in a derived class, releases all resources held by the current WaitHandle. Releases all resources used by the current instance of the WaitHandle class. Releases the Mutex once. Blocks the current thread until the current WaitHandle receives a signal.
Metodele folosite cu Mutex sunt: WaitOne pentru a bloca accesul ReleaseMutex pentru eliberare. Metodele Close() sau Dispose() fac automat ReleaseMutex.
ReleaseMutex este apelat numai de firul proprietar.
Asiminoaei Ioan
Exemplu (MSDN)
// // // // This example shows how a Mutex is used to synchronize access to a protected resource. Unlike Monitor, Mutex can be used with WaitHandle.WaitAll and WaitAny, and can be passed across AppDomain boundaries.
using System; using System.Threading; class Test { // Create a new Mutex. The creating thread does not own the // Mutex. private static Mutex mut = new Mutex(); private const int numIterations = 1; private const int numThreads = 3; static void Main() { // Create the threads that will use the protected resource. for(int i = 0; i < numThreads; i++) { Thread myThread = new Thread(new ThreadStart(MyThreadProc)); myThread.Name = String.Format("Thread{0}", i + 1); myThread.Start();
Asiminoaei Ioan
Asiminoaei Ioan
Semaphore
Clasele sunt :
Semaphore ; SemaphoreSlim (nou in .NET 4.0).
The SemaphoreSlim class represents a lightweight, fast semaphore that can be used for waiting within a single process when wait times are expected to be very short. SemaphoreSlim relies as much as possible on synchronization primitives provided by the common language runtime (CLR). However, it also provides lazily initialized, kernel-based wait handles as necessary to support waiting on multiple semaphores. SemaphoreSlim also supports the use of cancellation tokens, but it does not support named semaphores or the use of a wait handle for synchronization. Un semafor poate fi asemanat ca o statie de spalat autoturisme: aceasta are o anumita capacitate; poate spala simultan un anumit numar de autoturisme dar nu mai mult decat capacitatea declarata. Semaforul este caracterizat prin doua argumente: unul indica capacitatea totala, iar celalalt indica numarul locurilor disponibile momentan. Observatie Semaforul cu capacitatea 1 este ca si un Mutex cu diferenta majora ca nu are un proprietar. Orice fir poate apela Release pe semaphore. Daca semaforul are nume poate fi folosit pentru sincronizare intre procese. Exemplu
class ServiceCar { static SemaphoreSlim _sem = new SemaphoreSlim (3); //Capacitate 3 static void Main() { for (int i = 1; i <= 5; i++) new Thread (Enter).Start (i); } static void Enter (object id) { Console.WriteLine (id + " doreste revizie"); _sem.Wait(); Console.WriteLine (id + " este in service!"); Thread.Sleep (1000 * (int) id); Console.WriteLine (id + " pleaca din service!"); _sem.Release();}}
Asiminoaei Ioan
Clasa AutoResetEvent
Folosita in sincronizarea firelor. Notifica un fir in asteptare ca a aparut un anumit eveniment. In .NET Framework 2.0, AutoResetEvent este derivata din clasa EventWaitHandle. Un AutoResetEvent este echivalent din punct de vedere functional cu EventWaitHandle creat cu EventResetMode.AutoReset. Metode: Metoda Set pentru a trece in starea semnalat. Metoda WaitOne il trece in starea nesemnalat daca era in starea semnalat, altfel se asteapta. Pot fi folosite si metodele statice WaitAll si WaitAny. Clasa nu poate fi mostenita.
public sealed class AutoResetEvent : WaitHandle
AutoResetEvent permite firelor sa comunice intre ele prin semnalizare. Aceasta comunicatie este legata de accesul la o resursa la care firele au nevoie de acces exclusiv. Un fir asteapta un semnal apeland WaitOne() pe AutoResetEvent. Daca AutoResetEvent este in starea nesemnalat, firul se blocheaza, asteapta ca firul ce controleaza acea resursa sa se termine sau sa treaca evenimentul in starea semnalat, firul din urma apeland metoda Set(). Cand obiectul devine semnalat, firul ce era in asteptare pe acel obiect va fi activat, si obiectul devine din nou nesemnalat. Daca nici un fir nu asteapta pe un obiect semnalat, acest obiect va ramane in starea semnalat. Starea initiala a unui AutoResetEvent poate fi controlata printr-o valoare pasata ctor : true = stare initiala semnalata false = stare initiala nesemnalata. AutoResetEvent poate fi folosit cu metodele statice WaitAll si WaitAny. Metoda WaitHandle.WaitAll. Prototipuri.
public static bool WaitAll(WaitHandle[]);
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 40/51 Asteapta ca toate elementele din tabloul specificat sa primeasca un semnal, folosind in plus o valoare Int32 pentru a masura un interval de timp, si daca sincronizarea sa se termine inainte de asteptare.
public static bool WaitAll(WaitHandle[], TimeSpan, bool);
Observatie WaitAny are aceeasi functionalitate ca si WaitAll, diferenta fiind ca se asteapta ca cel putin un element din tablou sa devina semnalat.
Asiminoaei Ioan
ManualResetEvent
Evenimente cu resetare manuala. Metode:
Reset() pentru a trece evenimentul in starea nesemnalat. Firul care asteapta foloseste metoda WaitOne() pe eveniment. Un fir trece event in starea semnalat folosind metoda Set().
Metode importante din aceasta clasa. Sets the state of the specified event to nonsignaled. Set Sets the state of the specified event to signaled. WaitOne (inherited from WaitHandle) Overloaded. When overridden in a derived class, blocks the current thread until the current WaitHandle receives a signal. Exemplu: de urmarit metodele Set si WaitAll
using using using using System; System.IO; System.Security.Permissions; System.Threading; Reset
// Request permission to create and write files to C:\TestTest. [assembly: FileIOPermissionAttribute(SecurityAction.RequestMinimum, All = "C:\\TestTest")] class Test { static void Main() { const int numberOfFiles = 5; string dirName = "C:\\TestTest"; string fileName; byte[] byteArray; Random randomGenerator = new Random(); // se declara un array de 5 evenimente manuale ManualResetEvent[] manualEvents = new ManualResetEvent[numberOfFiles]; // Informatie ce o pasez firului. // Clasa State este definita mai jos in cod.
Asiminoaei Ioan
Asiminoaei Ioan
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 44/51 Exemplu pentru WaitOne. Se calculeaza suma a trei numere (MSDN).
using System; using System.Threading; class CalculateTest { static void Main() { Calculate calc = new Calculate(); Console.WriteLine("Result = {0}.", calc.Result(234).ToString()); Console.WriteLine("Result = {0}.", calc.Result(55).ToString()); } } class Calculate { double baseNumber, firstTerm, secondTerm, thirdTerm; AutoResetEvent[] autoEvents; ManualResetEvent manualEvent; // Generate random numbers to simulate the actual calculations. Random randomGenerator; public Calculate() { autoEvents = new AutoResetEvent[] { new AutoResetEvent(false), new AutoResetEvent(false), new AutoResetEvent(false) }; manualEvent = new ManualResetEvent(false); } void CalculateBase(object stateInfo) { baseNumber = randomGenerator.NextDouble(); // Signal that baseNumber is ready. manualEvent.Set(); } // The following CalculateX methods all perform the same // series of steps as commented in CalculateFirstTerm. void CalculateFirstTerm(object stateInfo) { // Perform a precalculation. double preCalc = randomGenerator.NextDouble(); // Wait for baseNumber to be calculated. manualEvent.WaitOne();
Asiminoaei Ioan
Asiminoaei Ioan
Asiminoaei Ioan
Clasa BackgroundWorker
The BackgroundWorker type exposes the following members.
Constructor
Name
BackgroundWorker
Description
Initializes a new instance of the BackgroundWorker class.
Proprietati
Name WorkerReportsProgress
Description
Gets or sets a value indicating whether the BackgroundWorker can report progress updates. Gets or sets a value indicating whether the BackgroundWorker supports asynchronous cancellation.
WorkerSupportsCancellation
Metode
Name OnDoWork OnProgressChanged OnRunWorkerCompleted ReportProgress(Int32) ReportProgress(Int32, Object) RunWorkerAsync() RunWorkerAsync(Object)
Description
Raises the DoWork event. Raises the ProgressChanged event. Raises the RunWorkerCompleted event. Raises the ProgressChanged event. Raises the ProgressChanged event. Starts execution of a background operation. Starts execution of a background operation.
Evenimente
Name Disposed
Description
Occurs when the component is disposed by a call to the Dispose
Asiminoaei Ioan
Occurs when RunWorkerAsync is called. Occurs when ReportProgress is called. Occurs when the background operation has completed, has been canceled, or has raised an exception.
Exemplu: BackgroundWorker
Folosire BackgroundWorker: pasii minimi necesari sunt comentati in cod
using using using class { System; System.Threading; System.ComponentModel; Program static BackgroundWorker _bw; static void Main() { // Pas 1. Creare BW _bw = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true }; // Pas 2. Tratez evenimentul DoWork metoda bw_DoWork _bw.DoWork += bw_DoWork; // Pas 3. Eveniment ProgressChanged _bw.ProgressChanged += bw_ProgressChanged; // Pas 4. Eveniment actiune terminata _bw.RunWorkerCompleted += bw_RunWorkerCompleted; // Pas 5. Lansare fir backgroundWorker _bw.RunWorkerAsync ("Hello to worker"); Console.WriteLine ("Press Enter in the next 5 seconds to cancel"); Console.ReadLine(); if (_bw.IsBusy) _bw.CancelAsync(); Console.ReadLine(); }
Asiminoaei Ioan
Asiminoaei Ioan
Problema fire
Presupunem urmatorul scenariu. La nivelul unei aplicatii in C# exista o lista dubla inlantuita. Se vor efectua operatii (adaugare, eliminare, modificare nod) asupra acestei liste din fire diferite din cadrul aceluiasi proces. Fiecare fir va avea acces la urmatoarele informatii : lista ; nodul de inceput al listei ; nodul final al listei. Se vor crea minimum trei fire (in afara firului principal) ce vor executa aceste operatii. Va imaginati scenariul pentru efectuarea diverselor operatii (adaugare, eliminare, modificare). In final firul principal va afisa lista, adica fiecare nod cu valoarea respectiva precum si lista cu nodurile eliminate. Nodul listei ar putea avea urmatoarea structura (campurile obligatorii se pastreaza): class Nod { string Valoare ; // camp obligatoriu string Key ; // camp obligatoriu; valoare unica pentru identificare nod Nod next ; // camp optional; legatura la nodul urmator Nod prev; // camp optional; legatura la nodul anterior } Campul Valoare va contine numele firului care a creat / modificat acel nod si momentul de timp cand a fost efectuata operatia. Acest camp va mentine informatia in mod cumulativ, adica la valoarea existenta se va adauga (concatena) noua valoare (se aplica pentru operatiile de modificare si eliminare nod). Operatia de eliminare va salva nodurile eliminate in alta lista. Idei de implementare (nu e obligatoriu sa le folositi). Metoda pentru fir poate sa execute cod intr-o bucla while controlata de o variabila numita CanContinue, variabila gestionata de firul principal : Codul ar putea fi scris astfel : ... while(CanContinue) { // cod pentru determinare operatie (adaugare, eliminare, etc.) // cod pentru operatia selectata }
Asiminoaei Ioan
Fire de executie si sincronizare Pag. 51/51 sau cod executat intr-o bucla for (nr de noduri sa nu fie mai mare de 5). La lansarea firului variabila CanContinue va fi setata pe true. Setarea pe false a acestei variabile va duce la terminarea firului / firelor. Daca optati pentru bucla for nu mai e nevoie de variabila CanContinue. Pentru lista dubla inlantuita puteti folosi LinkedList generica. In acest caz primul si ultimul nod il puteti lua din proprietati. Studiati metodele expuse de acest tip (LinkedList) pentru a le folosi in operatiile cerute.
Asiminoaei Ioan