Kada koristiti Task.WaitAll vs. Task.WhenAll u .NET

TPL (knjižnica paralelnih zadataka) jedna je od najzanimljivijih novih značajki dodanih u novijim verzijama .NET okvira. Metode Task.WaitAll i Task.WhenAll dvije su važne i često korištene metode u TPL-u.

Task.WaitAll blokira trenutnu nit dok svi ostali zadaci ne dovrše izvršenje. Metoda Task.WhenAll koristi se za stvaranje zadatka koji će se dovršiti ako i samo ako su svi ostali zadaci dovršeni.

Dakle, ako koristite Task.WhenAll dobit ćete objekt zadatka koji nije dovršen. Međutim, neće blokirati, već će omogućiti izvršavanje programa. Naprotiv, poziv metode Task.WaitAll zapravo blokira i čeka da se svi ostali zadaci dovrše.

U osnovi, Task.WhenAll dat će vam zadatak koji nije dovršen, ali možete koristiti ContinueWith čim navedeni zadaci dovrše svoje izvršavanje. Imajte na umu da ni Task.WhenAll ni Task.WaitAll zapravo neće pokretati zadatke; tj. ovim metodama se ne započinju zadaci. Evo kako se ContinueWith koristi s Task.WhenAll: 

Task.WhenAll (taskList) .ContinueWith (t => {

  // ovdje napišite svoj kod

});

Kao što navodi Microsoftova dokumentacija, Task.WhenAll "stvara zadatak koji će se dovršiti kada svi objekti Task u nebrojenoj zbirci budu dovršeni."

Task.WhenAll nasuprot Task.WaitAll

Dopustite mi da na jednostavnom primjeru objasnim razliku između ove dvije metode. Pretpostavimo da imate zadatak koji izvodi neku aktivnost s nitom korisničkog sučelja - recimo, neka animacija mora biti prikazana u korisničkom sučelju. Sada, ako koristite Task.WaitAll, korisničko će sučelje biti blokirano i neće se ažurirati dok svi povezani zadaci ne budu dovršeni i blok ne bude pušten. Međutim, ako koristite Task.WhenAll u istoj aplikaciji, nit korisničkog sučelja neće biti blokirana i ažurirat će se kao i obično.

Dakle, koju od ovih metoda biste trebali koristiti kada? Pa, možete koristiti WaitAll kad se namjera sinkrono blokira da biste dobili rezultate. Ali kada biste željeli iskoristiti asinkronost, htjeli biste upotrijebiti varijantu WhenAll. Možete pričekati Task.WhenAll bez potrebe da blokirate trenutnu nit. Stoga ćete možda htjeti koristiti await s Task.WhenAll unutar async metode.

Dok Task.WaitAll blokira trenutnu nit dok svi zadaci na čekanju ne budu dovršeni, Task.WhenAll vraća objekt zadatka. Task.WaitAll baca AggregateException kad jedan ili više zadataka baci iznimku. Kada jedan ili više zadataka izuzmu iznimku i pričekate metodu Task.WhenAll, ona odmotava AggregateException i vraća samo prvu.

Izbjegavajte upotrebu Task.Run u petljama

Možete koristiti zadatke kada želite istodobno izvršavati aktivnosti. Ako trebate visok stupanj paralelizma, zadaci nikad nisu dobar izbor. Uvijek je poželjno izbjegavati korištenje niti spremišta niti u ASP.Netu. Stoga biste se trebali suzdržati od upotrebe Task.Run ili Task.factory.StartNew u ASP.Net.

Task.Run uvijek treba koristiti za CPU vezani kôd. Task.Run nije dobar izbor u ASP.Net aplikacijama ili aplikacijama koje koriste ASP.Net vrijeme izvođenja jer samo prebacuje posao u nit ThreadPool. Ako koristite ASP.Net Web API, zahtjev bi već koristio nit ThreadPool. Dakle, ako koristite Task.Run u svojoj aplikaciji ASP.Net Web API, samo ograničavate skalabilnost prebacivanjem djela u drugu radničku nit bez ikakvog razloga.

Imajte na umu da postoji nedostatak u korištenju Task.Run u petlji. Ako koristite metodu Task.Run unutar petlje, stvorit će se više zadataka - po jedan za svaku jedinicu rada ili iteracije. Međutim, ako koristite Parallel.ForEach umjesto korištenja Task.Run unutar petlje, particioner se stvara kako bi se izbjeglo stvaranje više zadataka za izvođenje aktivnosti nego što je potrebno. To bi moglo značajno poboljšati izvedbu jer možete izbjeći previše prebacivanja konteksta i pritom iskoristiti više jezgri u svom sustavu.

Treba napomenuti da Parallel.ForEach interno koristi Partitioner kako bi kolekciju distribuirao u radne predmete. Inače, ova se distribucija ne događa za svaki zadatak na popisu stavki, već se događa u paketu. To smanjuje troškove koji su uključeni i time poboljšava performanse. Drugim riječima, ako koristite Task.Run ili Task.Factory.StartNew unutar petlje, oni će izraditi nove zadatke izričito za svaku iteraciju u petlji. Parallel.ForEach je mnogo učinkovitiji jer će optimizirati izvršavanje raspodjelom radnog opterećenja na više jezgri u vašem sustavu.