Sunteți pe pagina 1din 14

16.

Fire de execuie i programare asincron


I. Crearea firelor de execuie Pentru a simplifica proiectarea unei aplicaii se poate aplica o strategie de tip divide-et-impera, prin care un sistem complex se mparte n uniti de execuie mai mici, numite fire de execuie. Se beneficiaz astfel de o cretere a concurenei i de utilizarea mai eficient a procesorului. Spaiul de nume System.Threading conine clasele din .NET FCL necesare pentru crearea i gestionarea firelor de execuie. O instan a clasei ThreadStart (o clas delegat) va ncapsula metoda pe care o va executa o instan a clasei Thread (adic un fir de execuie). Clasa Thread poate fi instaniat n mod uzual, cu new, sau se poate executa metoda static Thread.CurrentThred pentru a obine o referin la firul de execuie curent. Execuia unui fir se pornete cu metoda Start.
using System; using System.Threading; namespace pnoro{ class Aplicatie{ static void F(){ Console.WriteLine("{0}F()...", Thread.CurrentThread.Name); } static void Main(){ //se obtine o referinta la firul de executie curent Thread t1 = Thread.CurrentThread; //se seteaza proprietatea Name t1.Name = "[Main thread] -> "; //delegatul incapsuleaza metoda pe care o va apela //un fir de executie ThreadStart ts = new ThreadStart(F); //se creeaza un nou fir de executie, //specificand obiectul ThreadStart Thread t2 = new Thread(ts); //se seteaza proprietatea Name t2.Name = "[Other thread] -> "; //se porneste al doilea fir de executie, //concurent cu firul principal t2.Start(); //firul principal Console.WriteLine("{0}Main()...", t1.Name); Console.ReadLine();

}; }

Executnd acest program, se observ faptul c mesajul din Main() se afieaz naintea celui din al doilea fir:
[Main thread] -> Main()... [Other thread] -> F()...

ceea ce demonstreaz c al doilea fir de execuie se execut asincron cu primul! II. Sincronizarea firelor de execuie Metoda static Thread.Sleep permite oprirea activitii unui fir pentru un numr de mili-secunde, numr specificat ca argument; aceast metod acioneaz doar asupra firului curent (ea apeleaz intern Thread.CurrentThread). Valoarea 0 pentru argument specific faptul c firul curent i suspend execuia pentru a permite altor fire, aflate n ateptare, s-i continue execuia; valoarea Timeout.Infinite pentru argument specific blocarea pe termen nedefinit a firului curent. Metoda Join permite sincronizarea a dou fire de execuie: firul curent se blocheaz n ateptarea terminrii firului asupra cruia se execut metoda:
using System; using System.Threading; namespace pnoro{ class Aplicatie{ static void F(){ Console.WriteLine("{0}F()...", Thread.CurrentThread.Name); Console.WriteLine("{0}Sleeping 3 seconds...", Thread.CurrentThread.Name); //oprire de 3 secunde Thread.Sleep(3000); Console.WriteLine("{0}Waking up...", Thread.CurrentThread.Name); } static void Main(){ Thread t1 = Thread.CurrentThread; t1.Name = "[Main thread] -> "; ThreadStart ts = new ThreadStart(F); Thread t2 = new Thread(ts); t2.Name = "[Other thread] -> "; t2.Start(); //firul principal Console.WriteLine("{0}Main()...", t1.Name); //primul fir (in care se executa Join) //asteapta terminarea celui de-al doilea fir //(asupra caruia se executa Join) t2.Join(); Console.WriteLine("{0}Press ENTER to terminate!", t1.Name); Console.ReadLine(); } }; }

Clasa Thread ofer i o metod Suspend, care se poate aplica att firului curent, ct i altui fir ; execuia unui fir suspendat va fi reluat doar prin intermediul altui fir, care s execute metoda Resume. Dac un fir este deja suspendat, un al doilea apel Suspend nu are efect; indiferent ct apeluri Suspend s-au executat asupra unui fir, acesta i va relua execuia la primul apel Resume:
using System; using System.Threading; namespace pnoro{ class Aplicatie{ static void F(){ Console.WriteLine("{0}F()...", Thread.CurrentThread.Name); Console.WriteLine("{0}Working...", Thread.CurrentThread.Name); Thread.Sleep(2000); Console.WriteLine("{0}Finished!", Thread.CurrentThread.Name); Thread.Sleep(1000); } static void Main(){ Thread t1 = Thread.CurrentThread; t1.Name = "[Main thread] -> "; ThreadStart ts = new ThreadStart(F); Thread t2 = new Thread(ts); t2.Name = "[Other thread] -> "; t2.Start(); //se suspenda executia celui de-al doilea fir t2.Suspend(); //al doilea apel nu face nimic t2.Suspend(); //firul principal Console.WriteLine("{0}Main()...", t1.Name); Console.WriteLine("{0}Press ENTER to terminate!", t1.Name); Console.ReadLine(); //reluarea executiei celui de-al doilea fir t2.Resume(); } }; }

III.Distrugerea firelor de execuie Un fir de execuie se distruge cu metoda Abort. Mediul de execuie foreaz terminarea unui fir prin generarea unei excepii de tipul ThreadAbortException; respectivul fir poate prinde acest excepie i poate efectua operaii de dealocare a resurselor, ns nu poate refuza terminarea.
using System; using System.Threading; namespace pnoro{ class Aplicatie{ static void F(){ try{ Console.WriteLine("{0}F()...", Thread.CurrentThread.Name); Console.WriteLine("{0}Working...", Thread.CurrentThread.Name); Thread.Sleep(7000);

} catch (Exception e){ Console.WriteLine("{0}Caught exception {1}", Thread.CurrentThread.Name, e.GetType()); } finally{ Console.WriteLine("{0}Cleaning...", Thread.CurrentThread.Name); Thread.Sleep(1000); Console.WriteLine("{0}Done!", Thread.CurrentThread.Name); }

Console.WriteLine("{0}Finished!", Thread.CurrentThread.Name); Thread.Sleep(1000);

}; }

static void Main(){ Thread t1 = Thread.CurrentThread; t1.Name = "[Main thread] -> "; ThreadStart ts = new ThreadStart(F); Thread t2 = new Thread(ts); t2.Name = "[Other thread] -> "; t2.Start(); Console.WriteLine("{0}Main()...", t1.Name); Thread.Sleep(3000); Console.WriteLine("{0}Tired of waiting... sending Abort!", t1.Name); //terminarea celui de-al doilea fir t2.Abort(); Console.ReadLine(); }

Odat distrus, un fir nu mai poate fi repornit (chiar dac referina la obiectul Thread este, n continuare, valid!). Pentru a distruge un fir, mediul de execuie ateapt pn cnd acesta atinge o stare sigur; cu alte cuvinte, nu se poate preciza exact momentul cnd un fir va fi efectiv distrus (el i poate continua execuia, pentru o vreme, i dup ce asupra sa s-a executat Abort). Dac firul curent trebuie s-i continue execuia dup ce un fir a fost efectiv distrus, apelul Abort va fi urmat de apelul Join. IV. Prioritatea firelor de execuie Procesul alegerii urmtorului fir care se va executa nu este aleatoriu: fiecare fir de execuie are asociat o prioritate, implicit Normal. Prioritatea unui fir de execuie se poate obine sau modifica prin intermediul proprietii Thread.Priority; aceast proprietate accept valori de tipul ThreadPriority, o enumerare ce definete constantele Highest, AboveNormal, Normal, BelowNormal i Lowest.

Executnd urmtorul program:


using System; using System.Threading; namespace pnoro{ class Aplicatie{ static void F1(){ Console.WriteLine("{0}F1() started...", Thread.CurrentThread.Name); Console.WriteLine("{0}Working...", Thread.CurrentThread.Name); for (int i=1; i<10; ++i){ Console.WriteLine("{0}i={1}",Thread.CurrentThread.Name, i); int j = -100000000; while (j<0){ ++j; int x; x = j; } } Console.WriteLine("{0}Finished!", Thread.CurrentThread.Name); } static void F2(){ Console.WriteLine("{0}F2() started...", Thread.CurrentThread.Name); Console.WriteLine("{0}Working...", Thread.CurrentThread.Name); for (int i=1; i<10; ++i) { Console.WriteLine("{0}i={1}",Thread.CurrentThread.Name, i); int j = -100000000; while (j<0){ ++j; int x; x = j; } } Console.WriteLine("{0}Finished!", Thread.CurrentThread.Name); } static void Main(){ ThreadStart ts1 = new ThreadStart(F1); ThreadStart ts2 = new ThreadStart(F2); Thread t1 = new Thread(ts1); Thread t2 = new Thread(ts2); t1.Name = "[First thread] -> "; t2.Name = "[Second thread] -> "; t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("Main finished ... Press ENTER!"); Console.ReadLine(); }

}; }

se obine:
[First thread] -> F1() started. [First thread] -> Working... [First thread] -> i=1 [Second thread] -> F2() started [Second thread] -> Working... [Second thread] -> i=1 [First thread] -> i=2

[Second thread] -> i=2 [First thread] -> i=3 [Second thread] -> i=3 [Second thread] -> i=4 [First thread] -> i=4 [Second thread] -> i=5 [First thread] -> i=5 [First thread] -> i=6 [Second thread] -> i=6 [First thread] -> i=7 [Second thread] -> i=7 [First thread] -> i=8 [Second thread] -> i=8 [First thread] -> i=9 [Second thread] -> i=9 [Second thread] -> Finished! [First thread] -> Finished! Main finished ... Press ENTER!

i se observ (datorit ntreeserii mesajelor) c cele dou fire sunt programate pentru execuie n mod egal. Dnd ns primului fir un nivel mai nalt de prioritate:
static void Main(){ ThreadStart ts1 = new ThreadStart(F1); ThreadStart ts2 = new ThreadStart(F2); Thread t1 = new Thread(ts1); Thread t2 = new Thread(ts2); t1.Name = "[First thread] -> "; t2.Name = "[Second thread] -> "; //prioritate superioara pentru primul fir t1.Priority = ThreadPriority.AboveNormal; //prioritatea celui de-al doilea fir ramane Normal //t2.Priority = ThreadPriority.Normal; t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("Main finished ... Press ENTER!"); Console.ReadLine(); }

se obine:
[First thread] -> F1() started... [First thread] -> Working... [First thread] -> i=1 [First thread] -> i=2 [First thread] -> i=3 [First thread] -> i=4 [First thread] -> i=5 [First thread] -> i=6 [First thread] -> i=7 [First thread] -> i=8 [First thread] -> i=9 [First thread] -> Finished! [Second thread] -> F2() started... [Second thread] -> Working... [Second thread] -> i=1 [Second thread] -> i=2 [Second thread] -> i=3 [Second thread] -> i=4 [Second thread] -> i=5 [Second thread] -> i=6

[Second thread] -> i=7 [Second thread] -> i=8 [Second thread] -> i=9 [Second thread] -> Finished! Main finished ... Press ENTER!

V. Transmiterea de date ctre firele de execuie Deoarece metoda ncapsulat de ctre delegatul ThreadStart nu accept argumente, o posibilitate de a transmite date ctre un fir de execuie const n a ncapsula ntr-o clas aceast metod mpreun cu datele asupra creia acioneaz:
using System; using System.Threading; namespace pnoro{ class PrelucrareInformatii{ //informatiile transmise firului int a; int b; //rezultatul obtinut in urma executiei firului int suma; public PrelucrareInformatii(int a, int b){ this.a = a; this.b = b; } public int Suma{ get{ return suma; } } //metoda ce va fi executata de fir public void Add(){ suma = a+b; Thread.Sleep(1000); }

};

class Aplicatie{ static void Main(){ PrelucrareInformatii pi = new PrelucrareInformatii(2,3); Thread t = new Thread(new ThreadStart(pi.Add)); Console.WriteLine("[Main] Waiting for Add..."); t.Start(); t.Join(); Console.WriteLine("[Main] The result is {0}", pi.Suma); Console.WriteLine("Main finished ... Press ENTER!"); Console.ReadLine(); } }; }

VI. Sincronizarea accesului firelor de execuie n seciunile critice Atunci cnd o metod acioneaz asupra unui obiect, acesta se poate afla, temporar, ntr-o stare nesigur (n sensul c anumite cmpuri pot fi modificate, fr a fi efectuat nc toate corelrile necesare cu restul cmpurilor). Dac exist un singur
7

fir de execuie, asupra unui obiect se poate executa o singur metod la un anumit moment, ceea ce face la la execuia urmtoarei metode obiectul s se afle ntr-o stare sigur. Dac ns exist mai multe fire de execuie, mai multe metode se pot executa simultan asupra unui obiecte, cu (posibil) rezultate impredictibile. Apare astfel necesitatea de a garanta meninerea strii valide a unui obiect atunci cnd acesta este utilizat simultan de mai multe fire de execuie; acest lucru se realizeaz prin definirea de seciuni critice de cod, care pot fi accesate doar de ctre un singur fir. Clasa System.Monitor serializeaz accesul la un bloc de cod. Metoda static Monitor.Enter marcheaz nceputul unei zone protezate de cod, prin achiziionarea unui lact; dac alt fir de execuie deine deja lactul, metoda blocheaz n ateptarea eliberrii acestuia. Ieirea din zona protejat de cod se face cu metoda static Monitor.Exit, care elibereaz lactul anterior achiziionat:
using System; using System.Threading; namespace pnoro{ class Database{ public void UpdateDatabase(string nume, int varsta){ //inceputul sectiunii critice Monitor.Enter(this); Console.WriteLine("{0}Saving to MS SQL-Server database...", Thread.CurrentThread.Name); Console.WriteLine("Nume={0}\tVarsta={1}", nume, varsta); //operatia este consumatoare de timp Thread.Sleep(2000); //parasirea sectiunii critice Console.WriteLine("{0}Database updated!", Thread.CurrentThread.Name); Monitor.Exit(this); } }; class Aplicatie{ //obiectul Databse public static Database db = new Database(); //metoda executata de primul fir public static void F1(){ Console.WriteLine("{0} Started", Thread.CurrentThread.Name); db.UpdateDatabase("Ionescu", 33); Console.WriteLine("{0} Finished", Thread.CurrentThread.Name); } //metoda executata de al doilea fir public static void F2(){ Console.WriteLine("{0} Started", Thread.CurrentThread.Name); db.UpdateDatabase("Popescu", 42); Console.WriteLine("{0} Finished", Thread.CurrentThread.Name); }

}; }

static void Main(){ Thread t1 = new Thread(new ThreadStart(F1)); t1.Name = "[Fir1] -> "; Thread t2 = new Thread(new ThreadStart(F2)); t2.Name = "[Fir2] -> "; t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("Main finished ... Press ENTER!"); Console.ReadLine(); }

Executnd programul anterior, se obine:


[Fir1] -> Started [Fir1] -> Saving to MS SQL-Server database... Nume=Ionescu Varsta=33 [Fir2] -> Started [Fir1] -> Database updated! [Fir2] -> Saving to MS SQL-Server database... Nume=Popescu Varsta=42 [Fir1] -> Finished [Fir2] -> Database updated! [Fir2] -> Finished Main finished ... Press ENTER!

Deoarece metoda UpdateDatabase, de salvare a informaiilor ntr-o baz de date (foarte posibil, gzduit de un server accesat prin reea), este consumatoare de timp, un alt fir poate ncepe ncepe execuia aceleiai metode nainte ca primul fir s se termine, aa cum se observ la execuia programului. Deoarece operaia de salvare a informaiilor este protejat cu un lact, al doilea fir se blocheaz, ateptnd pn cnd primul fir elibereaz lactul. Aceast soluie sufer ns de o problem: este posibil ca, n urma apariiei unei excepii, primul fir de execuie s nu elibereze lactul. Pentru a remedia acest situaie, se utilizeaz un bloc try..finally, a.. s se garanteze execuia metodei Monitor.Exit chiar i n cazul apariiei unei excepii:
public void UpdateDatabase(string nume, int varsta){ try{ Monitor.Enter(this); Console.WriteLine("{0}Saving to MS SQL-Server database...", Thread.CurrentThread.Name); Console.WriteLine("Nume={0}\tVarsta={1}", nume, varsta); Thread.Sleep(2000); Console.WriteLine("{0}Database updated!", Thread.CurrentThread.Name); } finally{ Monitor.Exit(this); } }

Aceast tehnic are suport direct n limbajul C# prin intermediul instruciunii lock:
public void UpdateDatabase(string nume, int varsta){ lock(this){ Console.WriteLine("{0}Saving to MS SQL-Server database...", Thread.CurrentThread.Name); Console.WriteLine("Nume={0}\tVarsta={1}", nume, varsta); Thread.Sleep(2000); Console.WriteLine("{0}Database updated!", Thread.CurrentThread.Name); } }

Instruciunea lock genereaz cod care utilizeaz clasa Monitor i mpacheteaz codul marcat de acolade ntr-un bloc try..finally, identic n funcionalitate cu cel din exemplul anterior. O alt clas care se poate utiliza pentru a serializa accesul la o seciune critic este System.Threading.Mutex (mutual exclusiv): un singur fir poate deine/bloca, la un moment dat, un obiect mutex. Exist 4 versiuni de constructor pentru acest clas:
Mutex() Mutex(bool initialluOwned) Mutex(bool initialluOwned, string name) Mutex(bool initialluOwned, string name, out bool createdNew)

Prima form creeaz un mutex fr nume, deinut/blocat de firul curent. A doua form creeaz un mutex fr nume; valoarea true pentru argument specific faptul c mutexul este deinut/blocat de firul curent. A treia form specific, suplimentar fa de prima form, un nume pentru mutex. A patra form specific, suplimentar fa de a treia, un parametru de ieire; valoarea true semnific faptul c firului curent i s-a acordat deinerea iniial a mutexului. Metoda WaitOne este utilizat pentru ca firul curent s achiziioneze obiectul mutex asupra cruia se execut metoda. Firul care deine mutextul l elibereaz prin intermediul metodei Close:
class Database{ //obiectul mutex nu este detinut, initial, de nici un fir de executie static Mutex m = new Mutex(false); public void UpdateDatabase(string nume, int varsta){ //inceputul sectiunii critice //blocheaza firul curent pe termen nedefinit, //pana la achizitia mutextului m.WaitOne(); Console.WriteLine("{0}Saving in MS SQL-Server database...", Thread.CurrentThread.Name); Console.WriteLine("Nume={0}\tVarsta={1}", nume, varsta);

10

};

Thread.Sleep(2000); //parasirea sectiunii critice Console.WriteLine("{0}Database updated!", Thread.CurrentThread.Name); m.Close();

VII. Programare asincron prin intermediul delegailor Un delegat este o instan a clasei System.MulticastDelegate; acest clas conine metodele Invoke, BeginInvoke i EndInvoke. Dac Invoke este apelat uzual pentru a executa metoda callback pe care o ncapsuleaz un delegat, ultimile dou metode, mpreun cu interfaa IAsyncResult, sunt utilizate pentru a apela asincron metode. Se consider urmtorul exemplu:
using System; using System.Threading; namespace pnoro{ class Aplicatie{ //metoda callback public static int Add(int a, int b, out int sum){ Thread.Sleep(3000); return (sum = a + b); } public delegate int AddDelegate(int a, int b, out int sum); static void Main(){ int result; //un obiect delegat AddDelegate d = new AddDelegate(Add); //apelul sincron al metodei callback Console.WriteLine("Synchronous call for Add... Main is blocked!"); Console.WriteLine("Result: {0}", d(10,20, out result)); //apelul asincron al metodei callback Console.WriteLine("Asynchronous call for Add... Main is not blocked!"); //BeginInvoke returneaza un obiect IAsyncResult //primele 3 argumente sunt pasate metodei callback Add IAsyncResult sAA = d.BeginInvoke(10, 20, out result, null, null); for(int i=0; i<10; ++i){ Thread.Sleep(100); Console.WriteLine("Main working..."); } Console.WriteLine("Main is waiting for asynchronous Add to finish..."); //sincronizare: se blocheaza firul curent sAA.AsyncWaitHandle.WaitOne(); Console.WriteLine("Asynchronous Add finished..."); //primul argument EndInvoke corespunde //argumentului out acceptat de metoda callback //al doilea argument este starea apelului asincron int result2 = d.EndInvoke(out result, sAA); Console.WriteLine("Result: {0} {1}", result, result2); Console.WriteLine("Result: {0}", result); Console.WriteLine("Main finished ... Press ENTER!"); Console.ReadLine(); } }; }

11

Metoda callback Add, ncapsulat n delegatul AddDelegate, se apeleaz asincron cu firul de execuie curent prin intermediul metodei BeginInvoke; aceast metod creeaz un nou fir de execuie, n care se va executa metoda callback, i returneaz un obiect IAsyncResult, care ofer controlul asupra strii apelului asincron. BeginInvoke recepioneaz la nceput parametrii pe care trebuie s-i paseze metodei callback; n exemplul anterior, primii trei parametri. Al patrulea parametru (null, n exemplu) este o instan a clasei delegat System.AsyncCallback, prin intermediul creia mediul de execuie poate apela o metod la terminarea execuiei asincrone a metodei callback; n acest caz, noua metod recepioneaz ultimul argument al lui BeginInvoke, care este o instan Object. Sincronizarea ntre cele dou fire de execuie se realizeaz blocnd firul metodei Main, cu sAA.AsyncWaitHandle.WaitOne(), pn la terminarea execuiei asincrone a metodei callback. Metoda EndInvoke posed o semntur consistent cu cea a metodei callback: returneaz un int i accept cte un parametru out pentru fiecare parametru out acceptat de metoda callback (se pot astfel returna oricte valori, fr a fi necesar a le ncapsula ntr-o clas sau structur). Din acest motiv, aa cum se observ din exemplu, rezultatul se poate obine pe dou ci. Ultimii doi parametri ai lui BeginInvoke pot fi utilizai de metoda callback pentru a-i anuna terminarea execuiei. Al patrulea argument este un delegat AsyncCallback care ncapsuleaz o funcie, numit n urmtorul exemplu Announce, ce primete drept argument o instan IAsyncResult. Aceast instan este creeat de BeginInvoke prin ncapsularea celui de-al cincilea argument; mediul de execuie va livra instana lui Announce. Announce va recepiona instana IAsyncResult i va extrage din ea, prin intermediul proprietii AsyncState, al cincilea argument al lui BeginInvoke. Acest argument va fi chiar delegatul care ncapsuleaz metoda callback, permindu-i astfel lui Announce s apeleze EndInvoke!
using System; using System.Threading; namespace pnoro{ class Aplicatie{ //metoda callback public static int Add(int a, int b, out int sum){

12

}; }

} public delegate int AddDelegate(int a, int b, out int sum); //mediul de excutie apeleaza acesta metoda //la terminarea executiei metodei callback static void Announce(IAsyncResult saa){ Console.WriteLine("Asynchronous Add finished..."); //se extrage delegatul ce incapsuleaza metoda callback AddDelegate dd = (AddDelegate) saa.AsyncState; int result; //se extrage rezultatul executiei metodei callback dd.EndInvoke(out result, saa); Console.WriteLine("Result: {0}", result); } static void Main(){ int result; AddDelegate d = new AddDelegate(Add); //apelul asincron al metodei callback Console.WriteLine("Asynchronous call for Add... Main is not blocked!"); //delegatul ce incapsuleaza metoda callback (argumentul 5) //va fi incapsulat intr-un IAsyncResul si va fi //pasat de mediul de executie Announce (argumentul 4) d.BeginInvoke(10, 20, out result, new AsyncCallback(Announce), d); for(int i=0; i<10; ++i){ Thread.Sleep(200); Console.WriteLine("Main working..."); } Console.WriteLine("Main finished ... Press ENTER!"); Console.ReadLine(); }

Thread.Sleep(1000); return (sum = a + b);

Pentru a simplifica acest model se poate utiliza clasa AsyncResult din spaiul de nume System.Runtime.Remoting.Messaging; n acest caz, nu mai este nevoie s se paseze delegatul ce ncapsuleaz metoda callback drept al cincilea argument pentru BeginInvoke. Instana IAsyncResult recepionat de Announce se convertete ntr-o instan AsyncResult i se extrage din acesta delegatul prin intermediul proprietii AsyncDelegate:
using System; using System.Threading; using System.Runtime.Remoting.Messaging; namespace pnoro{ class Aplicatie{ //metoda callback public static int Add(int a, int b, out int sum){ Thread.Sleep(1000); return (sum = a + b); } public delegate int AddDelegate(int a, int b, out int sum); //mediul de excutie apeleaza acesta metoda //la terminarea executiei metodei callback static void Announce(IAsyncResult saa){ Console.WriteLine("Asynchronous Add finished..."); //se extrage delegatul ce incapsuleaza metoda callback

13

} static void Main(){ int result; AddDelegate d = new AddDelegate(Add); //apelul asincron al metodei callback Console.WriteLine("Asynchronous call for Add... Main is not blocked!"); d.BeginInvoke(10, 20, out result, new AsyncCallback(Announce), null); for(int i=0; i<10; ++i){ Thread.Sleep(200); Console.WriteLine("Main working..."); } Console.WriteLine("Main finished ... Press ENTER!"); Console.ReadLine(); }

AsyncResult ar = (AsyncResult)saa; AddDelegate dd = (AddDelegate) ar.AsyncDelegate; int result; //se extrage rezultatul executiei metodei callback dd.EndInvoke(out result, saa); Console.WriteLine("Result: {0}", result);

}; }

14