[ Shadowed @ 17.12.2014. 15:20 ] @
Ima li znacajne razlike izmedju:
Code (csharp):

static void Main(string[] args)
{
    DoSomething();
    Console.ReadLine();
}

public static async void DoSomething()
{
  StreamReader reader = new StreamReader(@"d:\test.txt");
  while(!reader.EndOfStream)
  {
    string line = await reader.ReadLineAsync();
    Console.WriteLine(line);
  }
}
 


i

Code (csharp):

static void Main(string[] args)
{
  Thread t = new Thread(DoSomething);
  t.Start();
  Console.ReadLine();
}

public static void DoSomething()
{
  StreamReader reader = new StreamReader(@"d:\test.txt");
  while(!reader.EndOfStream)
  {
    string line = reader.ReadLine();
    Console.WriteLine(line);
  }
}
 

?
[ mmix @ 17.12.2014. 15:37 ] @
async/await su dobri za grananje i redjanje i sinhronizaciju, to je samo jezicka semantika za klasicni threading (preko Task u Threading).

generalno nisu dobri za "fire one task" ili "fire and forget" scenarija. Vise su za scenarija "imam async task, koji poziva svoja tri async taska, koji pozivaju svojih n, i sve to moram da sinhronizujem"

[ tdusko @ 17.12.2014. 18:27 ] @
Async je cest izvor nejasnoca jer se ne retko objasnjava sa "To ti je kao da si napravio novi thread i tu stavio sta hoces da se izvrsi asinhrono". Tvoje pitanje me navodi na kontra pitanje. Da li mislis da se DoSomething() izvrsava u novom threadu zato sto imas async/await ili zato sto znas da je ReadLineAsync implementirana tako da se izvrsava u novom threadu? Ima na primer Task.Run() u sebi?

Poenta je da ne treba misliti da ako je neka metoda async da ce se ona izvrsiti u novom thread-u. Suprotno bi impliciralo da async zahteva multithreaded okruzenje sto je netacno. Ovo je primer sa msdn-a gde se async izvrsava u jednom thread-u:

Code:

async Task<int> AccessTheWebAsync()

    HttpClient client = new HttpClient();

    // GetStringAsync returns a Task<string>. That means that when you await the 
    // task you'll get a string (urlContents).
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

    // You can do work here that doesn't rely on the string from GetStringAsync.
    DoIndependentWork();

    // The await operator suspends AccessTheWebAsync. 
    //  - AccessTheWebAsync can't continue until getStringTask is complete. 
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync. 
    //  - Control resumes here when getStringTask is complete.  
    //  - The await operator then retrieves the string result from getStringTask. 
    string urlContents = await getStringTask;

    // The return statement specifies an integer result. 
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value. 
    return urlContents.Length;
}


I primedba:

Citat:
The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available


http://msdn.microsoft.com/en-US/library/vstudio/hh191443.aspx

Inace najveca zajebancija oko async-a je hvatanje Exception-a. Ko ce da hvata exception koji se desi u async metodi? Ako je isti thread kao caller, a kako ako je razliciti?
[ Shadowed @ 18.12.2014. 00:30 ] @
@tdusko, znam za takav primer. Zapravo to je prvo sto sam video o async/await i bilo mi je sasvim nezanimljivo pa sam u startu odbacio koncept. Sticajem okolnosti sam naisao na primer slican onom koji sam dao (samo nije imao petlju) i to je nesto sto mi je upotrebljivo. S druge strane, i sa obicnim thread-om postizem istu stvar a slozenost koda je maltene identicna. Zato sam kapirao da priupitam ima li nekog narocitog razloga da koristim async/await.

Inace, ja sam gornji primer malo pojednostavio, ono sto mi konkretno treba je da uradim long polling http request koji mi posle daje po jednu liniju na svakih par puta u sekundi do jednom u par sekundi i onda bih citao sa tog stream-a i umesto ovog pisanja u konzolu okidao event.
[ ravni @ 18.12.2014. 07:42 ] @
Posto spominjes HTTP, jedna prednost async metoda, bar teoretski, je sto dozvoljava laksi scaling.
Tj. ako implementacija async metoda koristi asinrone pozive operativnog sistema onda onaj ko ih koristi stedi threadove i moze da opsluzi vise zahteva.

Korisno u web okruzenju.
[ tdusko @ 18.12.2014. 09:01 ] @
Citat:
Shadowed: S druge strane, i sa obicnim thread-om postizem istu stvar a slozenost koda je maltene identicna. Zato sam kapirao da priupitam ima li nekog narocitog razloga da koristim async/await.
Ja licno uvek preferiram da izbegnem da se rucno bakcem sa thread-ovima ako ne moram. Ako isti code mogu da napisem sa async/await bez da platim nesto (citaj da budzim) onda nemam dilemu. Posebno ako koristimm async metode iz .net biblioteke.
[ djordjeno @ 18.12.2014. 22:02 ] @
Moje iskustvo je da async/await/task lagodnije radi u GUI delu (winforms) nego u poredjenju sa istom stvari implementiranom preko Thread-ova, Callback-ova ili BackgroundWorker-a.

Koliko sam razumeo callback od async taska sa FromCurrentSynchronizationContext se desava u thread-u odakle je task pozvan, tako da nema potrebe za explicitnim update-om Gui-a, kao sto je bio slucaj sa klasicnim System.Threading.Thread -om. Tj nema vise BeginInvoke-a ili InvokeRequired poziva da bi se rezultat iz jednog threada pokazao na formama (koje su poseban thread).
[ Dejan Carić @ 04.01.2015. 17:19 ] @
@Shadowed,

Malo kasnije dajem odgovor ali nadam se da ce biti koristan :)

Postoji razlika izmedju dva primera koja si naveo.

Thread klasa ti omogucava da kreiras OS-level thread na veoma low level-u. Sa Thread klasom imas punu kontrolu nad OS-level thread-om. Mozes da ga zaustavis, nastavis njegovo izvrsavanje, sacekas da se zavrsi, podesis prioritet i gomilu drugih stvari. Sa druge strane, kreiranje OS-level thread-a je veoma skupa operacija. Potrebno je 1MB memorije za svaki thread i postoji odredjeni CPU overhead za context-switching.

ThreadPool donekle reseava taj problem. On predstavlja grupu thread-ova kojim upravlja CLR. ThreadPool-u mogu da se posalju zadaci koji trebaju da se izvrse i tako se sprecava situacija da se kreira mnogo skupih OS-level thread-ova. Ako je ThreadPool pun, neki zadaci koji su poslati na izvrsavanje ce morati da sacekaju dok se ThreadPool ne oslobodi ponovo.
Nedostatak ThreadPool-a je sto samo mozes da podesis pool size i posaljes neki zadatak na izvrsavanje, ali nemas nikakvu kontrolu nad thread-ovima. Ne mozes sacekati da se zavrse, dohvatiti rezultat, itd. Cak ne mozes znati ni kada je tvoj zadatak poceo ili zavrsio sa izvrsavanjem.

Task klasa resava probleme koje imamo sa Thread i ThreadPool klasama. Taskovi se izvrsavaju iz ThreadPool-a, ne kreiraju se novi i skupi OS-level thread-ovi. Takodje mozes da sacekas da se task zavrsi, procitas vrednost, itd.
Ukoliko neki task dugo traje, nije pozeljno da blokira ThreadPool pa mozes da ga oznacis ili kao LongRunning ili da koristis await.
LongRunning ce da kreira OS-level thread umesto da koristi neki iz ThreadPool-a i treba ga koristiti iskljucivo za CPU operacije.
await se koristi za I/O operacije (citanje sa fajl sistema, pristup mrezi, itd.) Sa njim oslobadjas ThreadPool jer nema potrebe drzati thread zauzetim ako ne radi nista i ceka da se I/O operacija zavrsi.

U principu bi uvek trebao da koristis Task umesto Thread i da ga po potrebi oznacis sa LongRunning (ako u nekim narednim verzijama CLR ovo vec ne bude radio za tebe :))
Ne mogu da se setim ni jednog slucaja kada bi Thread trebao da se koristi umesto Task.


Malo cu izmeniti primer koji si postavio:
Code:

static void Main(string[] args)
{
    DoSomething();
    Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);
    Console.Read();
}

public static async void DoSomething()
{
    StreamReader reader = new StreamReader(@"H:\test.txt");
    while (!reader.EndOfStream)
    {
        string line = await reader.ReadLineAsync();
        Console.WriteLine(line);
    }

    Console.WriteLine("DoSomething: " + Thread.CurrentThread.IsThreadPoolThread);
    Console.WriteLine("DoSomething: " + Thread.CurrentThread.ManagedThreadId);
}


U Main metodi izvrsavam DoSomething metodu, ispisujem ID thread-a koji pokrece Main metodu i imam Console.Read() jer ne zelim da se Main metoda zavrsi ukoliko izvrsavanje DoSomething() metode jos uvek traje.
U DoSomething() metodi citam test.txt fajl koji sadrzi tri linije (Lorem, Ipsum, Dolor), ispisujem te linije na ekran kao i informaciju da li je thread koji izvrsava DoSomething metodu uzet iz ThreadPool-a kao i njegov ID.

Rezultat na ekranu:

Code:

Lorem
Ipsum
Main: 1
Dolor
DoSomething: True
DoSomething: 3


Da u Main metodi nisam imao Console.Read(), rezultat bi izgledao ovako:

Code:

Lorem
Ipsum
Main: 1
Dolor


Ukoliko zelim da se DoSomething metoda zavrsi pa tek onda da nastavim sa izvrsavanjem koda u Main metodi, DoSomething metoda bi morala da vrati Task umesto void, morao bih da koristim await u Main metodi i da Main metodu oznacim kao async:

Code:

static async void Main(string[] args)
{
    await DoSomething();
    Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);
}

public static async Task DoSomething()
{
    StreamReader reader = new StreamReader(@"H:\test.txt");
    while (!reader.EndOfStream)
    {
        string line = await reader.ReadLineAsync();
        Console.WriteLine(line);
    }

    Console.WriteLine("DoSomething: " + Thread.CurrentThread.IsThreadPoolThread);
    Console.WriteLine("DoSomething: " + Thread.CurrentThread.ManagedThreadId);
}


Ovo je nekada moglo da se koristi, sada u konzolnim aplikacijama Main metoda vise ne moze da se oznaci kao async.
Ali kod mozes da testiras u npr. web aplikacijama.


Nadam se da sam bar donekle pomogao da se resi misterija :)
[ mmix @ 04.01.2015. 21:10 ] @
Samo par korekcija

- ThreadPool threadovi su OS level threadovi, thread pool ih kreira on demand, ali ih ne unistava (vidi dalje za v4), ne postoje magicni NET threadovi, sve je samo stvar manipulacijom koda koji se izvesava u tim threadovima. U .NET 4 ovo je cak i promenjeno u i sada Thread pool radi sa dva poola threadova za razne kategorije poslova (IO i custom) i unistava OS threadove u zavisnosti od "pritiska" na pool. U IO intensive managed aplikacijama broj OS level threadova moze da naraste fino. Na mom systemu GetMaxThreads vraca 1023+1000 (work+IO) za 32bit app, 32768+1000 za 64bit, sto znaci da u teoriji moze da kreira maksimalno 33768 OS threadova u samo jednom svom procesu. Tesko da se moze pricati o ThreadPoolu kao o "ekonomicnoj" verziji threadova Samim tim propada i ideja o tome da ThreadPool moze da spreci lose napisan async kod da kreira brdo threadova.

- Alokacija fizicke memorije pri kreiranju threadova nije 1Mb, to je max rezervisani virtuelni prostor za stek, alociran u virtuelnom adresnom prostoru. TO samo po sebi nije nikakav problem i ne predstavlja zauzece fizicke memorije, virtuelni adresni prostor za 64bit sistem je 8TB po procesu, za 32bit proces 3Gb (a kao sto vidis po gornjem max threadu, .NET je spreman da zauzem 2023Mb od te memorije samo za threadove, tako da ni usteda memorije nije rezon za pool). Commited deo steka je po defaultu 8kb od tih 1Mb i raste kako raste stek. Tu je naravno i TLS, ali sam .NET ne zadire u to, ako ga kod koristi, koristi.

- .NET nema svoj thread context switching, niti je uputno to raditi jer je OS thread context switiching sasvim ok. Samim tim je thread switching overhead identican za sve. Ono sto .NET ima je task scheduling, sto nije isto, task scheduling je proces redjanja taskova na ulaz u thread pool da bi se optimizovao broj aktivnih threadova i bezbedno cuvali taskovi koji su blokirani u await state-u. Takodje, ono sto .NET ima je EXECUTION context switching, sto nije isto sto i thread switching (execution switch omogucava transparentno prebacivanje izvrsenja sa jednog threada na drugi).

- async/await rade preko default thread pool-a, tako da isti ne oslobadjaju thread pool poslova, sta vise zavise od njega.

- async/await je c# jezicka konstrukcija oko kreiranja i manipulacije Task objektima, Task objekat se kreira i registruje u poolu cak i kad koristis async void. Ako disasemblujes kod videces da u osnovi sve radi preko Taskova:
- od async methode kompajler u osnovi pravi nesto sto se zove AsyncStateMachine privatna klasa, ciju instancu kreira pri "pozivu" async metoda koristeci AsyncTaskMethodBuilder a cija kompleksnost je defakto odredjena brojem "izlaznih" tacaka, tj brojem await tacaka u metodi.
- poziv async metode kreira gornji state machine i startuje ga na default thread poolu.
- await jednostavno uzima GetAwaiter iz taska koji se ceka i vidi da li je isti zavrsio, ako jeste menja state i nastavlja metod, ako nije markira poslednje zavrseni state, registruje task kao pod-task Taska kojeg ceka da bi rekao TaskScheduleru da ga ne ubacuje u ciklus dok cekani task ne zavrsi. Kad sledeci put udje u izvrsenje, sto znaci da je await odradio, state machine ce (koristeci switch semantiku) skociti na prvu sledecu komandu iza await.
- koga interesuje kako to izgleda nek pogleda klasu kroz .NET reflector koristeci IL prikaz, sve se lepo vidi.

- Task+Long Running ima identican efekat kao Thread, uz gubitak kontrole nad threadom: https://coderkarl.wordpress.co...ong-running-tasks-and-threads/
- iz gornjeg razloga CLR nikad nece moci sam da deklarise Task kao long running.
- Thread treba da koristis umesto Task, imedju ostalog, kad je thread kritican i proces ne sme da se zavrsi dok se thread ne zavrsi. Po defaultu svi ThreadPool Task threadovi, ukljucujuci i kreirani long running, su background threadovi i ubijaju se po zavrsetku procesa. Drugi razlog je kad zelis da tvoj kod, cak i kad je blokiran, ostane u svom originalnom threadu. Posto thread pool koristi execution context switch, ne psotoji garancija da ce kod iza await biti izvrsen na istom threadu.

Prica je tu malo i komplikovanija kad se u pricu uvecde ExecutionContext, ali da ne tupimo do te mere. Generalno .NET ThreadPool i async/await su samo specijalizovana implementacija worker-crew paterna, nista manje nista vise, radi ispomoci programerima. Kao sto rece tdusko, ne postoji cak ni garancija da ce posao dobiti svoj thread i da nece biti uradjen na glavnom threadu, tvoj prvi sors npr kod mene daje ovaj rezultat:


Lorem
Ipsum
Dolor
DoSomething: False
DoSomething: 8
Main: 8

Sto znaci da je async rutina izvrsena sync, na glavnom threadu, tj nijedan await uDoSomething nije blokirao. Kad bih sebi dodao await Task.Delay(1) u metod, onda bi .NET uradio execution switch tog taska na worker thread i vratio se u Main, i dobio bih isti rezultat kao tvoj. To je i jedna od glavnih mana await/async mehnizma, ne ume da detektuje blocking mehanizam ako on nije awaitable (npr ubaci Thread.Sleep(10000) umesto await Task.Delay() i glavni thread ce biti blokiran 10 sekundi). Zato i .NET pere ruke od Taska i uvodi LongRunning flag pa ako imas sleep(10000), ti ga markiraj kao long running i dobices svoj thread

Samim tim, ako zaista ZELIS da nesto radi na drugom threadu, Thread objekat je jedino garantovano resenje.

[Ovu poruku je menjao mmix dana 04.01.2015. u 22:22 GMT+1]
[ mmix @ 04.01.2015. 21:53 ] @
Ovako npr izgleda state machine za tvoj prvi DoSomething, primeti state field i kako se menja kroz cikluse i kako se rezultat iz GetAwaiter() interpretira radi odlucivanja da li se nastavlja ili izlazi iz metode, i kako se pri sledecm pozivu uz switch/goto prebacuje na sledecu komandu. Isto primeti da je ceo ovaj kod Thread-agnostic, i da je postpuno nevazno na kom threadu se izvrsava. Na AwaitUnsafeOnCompleted metodi je da interno odluci sta dalje i na kom threadu.

Code (csharp):

private struct <DoSomething>d__0 : IAsyncStateMachine
{
    // Fields
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    private object <>t__stack;
    private TaskAwaiter<string> <>u__$awaiter3;
    public string <line>5__2;
    public StreamReader <reader>5__1;

    // Methods
    private void MoveNext()
    {
        try
        {
            bool flag = true;
            switch (this.<>1__state)
            {
                case -3:
                    goto Label_0125;

                case 0:
                    goto Label_0072;
            }
            this.<reader>5__1 = new StreamReader(@"s:\test.txt");
            while (!this.<reader>5__1.EndOfStream)
            {
                TaskAwaiter<string> awaiter = this.<reader>5__1.ReadLineAsync().GetAwaiter();
                if (awaiter.IsCompleted)
                {
                    goto Label_0090;
                }
                this.<>1__state = 0;
                this.<>u__$awaiter3 = awaiter;
                this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Program.<DoSomething>d__0>(ref awaiter, ref this);
                flag = false;
                return;
            Label_0072:
                awaiter = this.<>u__$awaiter3;
                this.<>u__$awaiter3 = new TaskAwaiter<string>();
                this.<>1__state = -1;
            Label_0090:
                string introduced7 = awaiter.GetResult();
                awaiter = new TaskAwaiter<string>();
                string str = introduced7;
                this.<line>5__2 = str;
                Console.WriteLine(this.<line>5__2);
            }
            Console.WriteLine("DoSomething: " + Thread.CurrentThread.IsThreadPoolThread);
            Console.WriteLine("DoSomething: " + Thread.CurrentThread.ManagedThreadId);
        }
        catch (Exception exception)
        {
            this.<>1__state = -2;
            this.<>t__builder.SetException(exception);
            return;
        }
    Label_0125:
        this.<>1__state = -2;
        this.<>t__builder.SetResult();
    }

    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine param0)
    {
        this.<>t__builder.SetStateMachine(param0);
    }
}
 
[ Dejan Carić @ 04.01.2015. 22:46 ] @
Niko ne kaze da postoje magicni threadovi vec da je koriscenje thread pool-a efikasnije od instanciranja novog threada.

Ako imas N thread-ova prilikom pokretanja procesa, jeftinije i brze je koristiti neki od postojecih thread-ova nego kreirati +1, zar ne? Razlike u preformansama nisu zanemarljive kada se radi o velikom broju kratkih taskova koje treba izvrsiti i to je jedan od razloga zasto je i napravljen ThreadPool. Naravno prica je dosta slozenija ali bitno je konstatovati da u suprotnom ThreadPool ne bi imao nikakvog smisla.

Kod I/O operacija ne zelimo da kreiramo novi thread i drzimo ga blokiranim dok ceka da se nesto izvrsi, vec zelimo da thread koji ceka na I/O operaciju vratimo thread pool-u, obavestimo ga kad je I/O operacija zavrsena i nastavimo izvrsavanje metode bilo sa istim ili nekim potpuno drugim thread-om. Nisu potrebne nikakve garancije da se posao nastavi na istom threadu vec iskljucivo dve stvari:
- ne treba se kreirati novi thread ako ne treba
- idle thread treba da radi nesto drugo ako moze umesto da ceka

Lepo si naveo da async / await kreairaju state masinu tako da ne razumem tvoju konstataciju da ne rasterecuju ThreadPool.

MS ne pere ruke od Taskova uvodjenjem LongRunning flaga. Task je apstrakcija za nesto sto je "obecanje" za nesto sto ce biti izvrseno. Implementacija zavisi od mnostvo stvari. ThreadPool gubi smisao ako se koristi za taskove koji traju dugo, zato se setovanjem tog flaga kreira novi thread koji nije pod kontrolom ThreadPool-a. Ne radi se o nikakvom pranju ruku.
[ mmix @ 05.01.2015. 01:11 ] @
ThreadPool nije efikasniji od instanciranja novog threada jer je cena upotrebe ThreadaPoola skuplja od upotrebe Thread objekta, a cena Execution Context switcha skuplja od OSovog Thread Switcha koji je manje vise pokriven hardverski u cipu. Nije efikasnije, samo je jednostavnije za programere, kao i svaki drugi vid apstrakcije sistemskih operacija. Pod uslovom naravno da ljudi skapiraju sta je zapravo async/await, sto nije tako lako kao sto izgleda. Dok ti ovo pisem, gledam metriku, trenutno na mom sistemu radi 167 procesa sa ukupno 1877 threadova, velika vecina njih suspended. Kreiranje i odrzavanje OS threadova je jeftino, komplikovano je sinhronizovati ih i ukomponovati ih u skladan tok i u tome je prednost await/async mehanizma nad koriscenjem OS primitiva, sto efektivno ZAOBILAZIS upotrebu mutexa, semafora i eventa tako sto imas neki svoj mehanizam cooperative multitaskinga (zato i pati od istih stvari kao i Win3.11, pati od tskova koji su long running ;)).
I sama ekipa u MSu je toga svesna, zato i v4 ne pokusava da zadrzi sve otvorene threadove. Svaki thread koji je kreiran u pool-u a nije u upotrebi mora da bude individualno suspendovan na OS nivou, tako da ti svejedno MORAS da ih blokiras i onda moras da ih odblokiras pre nego sto u njih uselis deo taska (execution context). Dakle, postoji trade-off, cesto je jeftinije uraditi -1, i onda +1 kad ponovo zatreba.

I ne znam odakle uopste ta ideja da mi ne zelimo blokiran OS thread? Potpuno je nevazno da li je OS thread u wait-statetu u OS scheduleru ili je Task ulancan iza awaitera u TaskScheduleru i tako drzan dalje od threadova, sve je to posao koji ne dobija CPU vreme i biva preskacen kako god da obrnes, samo je pitanje ko ga preskace, optimizovan linearni algoritam schedulera u OSu (O(n)) ili komplikovana hijerarhija odabira redosleda izvrsavanja u .NET TaskScheduleru (min O(nlogn))? Samim tim await/async ne rasterecuje niti opterecuje ThreadPool vise ili manje nego sto to radi neki mehanizam koji radi direktno sa threadovima ili cak mehanizam koji radi sa Task.Run operacijama koje se manuelno sinhronizuju. Await/async rasterecuje i program i ceo sistem nepotrebnog koriscenja OS primitiva, to je njegova glavna prednost. ThreadPool je jednostavno vucni konj koji obezbedjuje vezu izmedju Thread-agnostic async/await mehanizma i konkretnih OS threadova koji ce odradjivati posao, kroz TaskScheduler. Cak je i execuition context switch veca "faca" u toj prici od poola :) A sve to da bi programeri brze i lakse napisali asinhroni kod. Jednostavno, predajes preveliku vaznost thread poolu, iza await-async i Task-a je mogao (i moze) da stoji bilo koji drugi mehanizam, komotno moze da bude i mehanizam koji za svaki task kreira dedicated threadove i suspenduje ih dok cekaju mapirajuci svaki awaitable na jedan mutex. Ali time se naravno vracamo na pocetak.

I naravno da je pranje ruku za long running, TreadPool uopste ne gubi smisao za long running taskove, kao sto sam ti rekao on moze da izdri desetine hiljada long runing taskova. Async/await je taj koji gubi smisao sa long running taskovima zbog nacina na koji je napravljen (u async metodu se ulazi iz threada koji poziva, ne iz novog ili nekog od sekundarnih postojecih threadova iz poola) i zbog nacina na koji predaje kontrolu taskScheduleru (cooperative multitasking, mora da postoji awaitable implementacija za svaki block, ako ne postoji moras da je naspises sam).
[ Dejan Carić @ 05.01.2015. 08:28 ] @
Sa instanciranjem mnogo thread-ova otislo bi vise CPU vremena na context-switching nego na racunanje.
To je razlog zasto se ogranicava broj thread-ova koji moze da se kreira. Kada imas ogranicen broj threadova koje mozes da kreiras onda zelis da svi budu zaposleni umesto da cekaju / budu blokirani.
As simple as that :)

Ne bih da se pravim pametan, nisam nikad radio na razvoju operativnog sistema niti pisao nesto u asembleru.
Ali ako izguglamo thread vs threadpool i ljudi kao sto su Jon Skeet, Erik Lippert i drugi kazu da je kreiranje threadova skupa operacija i da je mnogo efikasnije koristiti thread pool umesto kreirati novi thread, a samo ti kazes da je ThreadPool glupost i mazanje ociju, meni je onda potpuno besmisleno da sirim pricu u nedogled.

Uostalom svako moze da kreira MVC aplikaciju sa i bez upotrebe async akcija, napravi load test i uporedi zauzece resursa, max broj request-ova koje aplikacija moze da podrzi, itd.
Naravno da za single request async / await ce biti samo overhead, ali kako broj request-ova raste tako se menja i rezultat u korist async / await-a.
[ mmix @ 05.01.2015. 09:54 ] @
Svaka cast Jonu i Eriku, ali oni su u osnovi c# gurui, bave se apstrakcijama i u prepevavaju ono sto su nauceni o sistemskim stvarima. Mnogo price o x86 platofrmi, izmedju ostalog o skupoci threadova i context switcha, poticu jos iz ranih dana. ThreadPool sasvim sigurno nije .NET izmisljotina, postoje C++ implementacije koje ulaze u trecu deceniju svog postojanja, i poticu iz vremena u kojima je bilo daleko vise smisla imati ih nego danas.

Od vremena 386-e mnogo stosta se promenilo:

- hardver je generalno ubrzan i postao je multi-cpu-multi-core, memorija je pojeftinila i drasticno se povecala, preslo se sa GB adresnih prostora na TB adresne prostore, moderni desktop sistemi mogu komotno da operisu sa 10-ak hiljada threadova, server platforme i sa 100ak jer je 99% njih skoro uvek u suspended/waiting stanju. Leba ne jedu sem prosotra koji je commited za stack i jedan KTHREAD entry u kernelu. Sam managed wrapper oko tih struktura u CLRu ce pojesti vise prostora i memorijski i CPU ciklusa za odrzavanje.
- thread context switch je DRASTICNO pojeftinio, najskuplji delovi tih operacija kao sto je TLB flush su ili optimizovani (proces-to-process switch) ili potpuno eliminisani (in-process-switch)
- TLB je sad multi-level, kao cache, sto omogucava sistemu da izbegava i proces-to-proces TLB flush u situacijama kad vise procesa "udara" u CPU
- Windows 7/2008r2 kernel je drasticno pojeftinio context switch na multicore masinama eliminisanjem dispatcher lock-a.
- thread koji je u blocked stanju (cekajuci primitivu) vise ne ucestvuje uopste u context switchu, scheduler ga jednostavno ignorise dok ga implementacija primitiva ne vrati u runnable state, tako da cena switchinga nema veze sa ukupnim brojem threadova vec sa brojem runnable threadova.
- Zbog nacina na koji x86 arhitektura generise sinhrinzujuce primitive (koriscenjem SpinLock-a sa atomicnim XCHG koji zakljucava magistralu za sva jezgra/procesore), najskuplje operacije danas su operacije sa primitivama, delom zato sto je spinlock spor, delom zato sto ometa izvrsavanje threadova na drugim jezgrima/procesorima. await/async je tu nasao svoje mesto (kao sto je npr optimistic lock preuzeo primat nad pesimistic lockom za SQL operacije).

Jedan od problema koji ThreadPool ima, a koji niko ne pominje, je upravo commited stack size. Thread koji je i dalje ziv a kome je prethodni task prosirio stack ce, do kraja svog zivota, zauzimati taj prostor u fizickoj memoriji. Ne postoji nacin da se stack shrinkuje sem da se thread ubije i napravi ponovo, cime se stack vraca na svojih 8kb. Drugi problem je u taskScheduleru koji ima overhead za odrzavanje liste cekanja koji se koristi pri apsolutno svakoj task operaciji a ne sluzi u praksi ama bas nicemu, bas da vidim to parce softvera koje ce potrositi 32768 threadova i doci u stiuaciju da mu je runnable task na cekanju. To su smesne price. Sta vise svojim ocima sam video custom TaskScheduler implementaciju koja nema listu cekanja vec razbacuje taskove direktno na threadove u thread pool-u u suspended stanju.

I na kraju, ja nisam rekao da je ThreadPool glupost, ja ga koristim svakodnevno, vec da ne resava problem "skupoce" threadova, samim tim tvrdnja "Ne mogu da se setim ni jednog slucaja kada bi Thread trebao da se koristi umesto Task" ne stoji. Jedan async thread (ili kontinuirani Task) ce biti, u idealnim uslovima, blokiran i preemptovan IDENTICAN broj puta od strane OS schedulera, nikakava .NET mahinacija threadovima to ne moze da spreci, moze samo da pogorsa. To je cena koju MORAS da platis, kako god. Ta cena i frekvencija kojom se desava je drasticno veca od cene formiranja novog threada. Async thread pool koji nije u upotrebi ce morati svejedno da bude blokiran u OS scheduleru, sto nije nista drugacije od dedicated threda koji je suspendovan. I onda na sve to dodas trosak exection context switch-a i taskScheulera koji je definitivno skupa operacija a koju ne mozes da izbegnes (sem preko LongRunning flaga) cak i kad imas klasican thread posao (bez await).

Dakle, sve sto sam ja rekao je da prica nije tako crno-bela kao sto je C# ljudi predstavljaju da reklamiraju ThreadPool, svet je postojao i pre C#-a, a iz nekog razloga vecina C# programera se ponasa kao da je 2001-a i kao da i dalje radimo na 386/486 masinama sa 16Mb rama. Thread pool je jednostavno apstrakcija i verovatno je optimizovaniji od neke budzevine koju bi napravio programer koji nije verziran sa async/multithreading programiranjem, i to je imho i vise nego dovoljno, da mogu da sednem za kod i da odmah krenem da radim async bez potrebe za nekim mojim overheadom. To i dalje nije razlog da mu se daju neka mitska obelezja.

[ mmix @ 05.01.2015. 10:26 ] @
Citat:
mmix:  Jedan async thread (ili kontinuirani Task) ce biti, u idealnim uslovima, blokiran i preemptovan IDENTICAN broj puta od strane OS schedulera, nikakava .NET mahinacija threadovima to ne moze da spreci, moze samo da pogorsa. To je cena koju MORAS da platis, kako god. Ta cena i frekvencija kojom se desava je drasticno veca od cene formiranja novog threada. Async thread pool koji nije u upotrebi ce morati svejedno da bude blokiran u OS scheduleru, sto nije nista drugacije od dedicated threda koji je suspendovan.


Da bih ti ovo objasnio plasticnije, da bi iskoristio prednost koju reklamiras, tri stvari moraju da se dese:

1. Trenutni deo taska (izmedju prethodnog i sledeceg awaita), zajedno sa execution context in and out switchom MORA da se desi unutar jednog kvanta OS threada. Dakle, mora da se izbegnu svi OS thread context switchevi sem ulaznog i izlaznog
1a. Direktno iz gornjeg, ne smes da koristis nijedan blocking mehanizam koji nije awaitable (npr thread sleep, ili custom blocking IO)
2. Svi kreirani threadovi u poolu moraju da budu highly-utilized (da efektivno nema idle threadova)

U tom trenutku (koji je prakticno nemoguc ili veoma veoma tesko ostvariv, izuzev za IO completion port) ce reuse threadova imati vise smisla od -1,+1 mehanizma. Medjutim, kad na tu pricu dodas vreme izvrsavanja managed koda koji sve to organizuje, i ta mala prednost se gubi.