A Task objektum a .NET 4.0-val mutatkozott be és mint láthattuk, egy eléggé univerzális megoldás. A Task által reprezentált folyamat futhat szinkron vagy aszinkron módon is. Tételezzük fel, hogy egy hálózati fájlletöltést kell megvalósÃtanunk a kódunkban. Ezt becsomagolhatjuk egy Task-ba, de minden egyes alkalommal a meghÃvásakor letölteni a fájlt felesleges lehet, fÅ‘leg, ha az nem változik olyan gyorsan. Ebben az esetben a fájlunkat érdemes egy helyi cache-ben tárolni. Ezzel a megoldással csak akkor kell ténylegesen letölteni a fájlt, ha az még nincs a cache-ben. Ha pedig ott van, akkor csak vissza kell adni.
Ebben a felhasználási környezetben a metódusunk egy Task, amire várakozhatunk, de ha egynél többször van meghÃvva, akkor az esetek többségében a cache miatt szinkron fog futni a metódusunk, vagyis a Task-ba becsomagolás az esetek többségében felesleges és extra memóriafoglalással jár, de a kód szépen olvasható marad. Ez az extra memóriafoglalás sűrűn ismétlÅ‘dÅ‘ I/O műveletek esetén már nem elhanyagolható költségű és ennek az áthidalására találták ki a ValueTask tÃpust a .NET Core 2.0-ban, ami ugyanazt a funkcionalitást biztosÃtja, mint egy Task, de struktúra alapú és szinkron futások esetén nem foglal magának extra memóriát. Nézzünk egy példát a használatára:
using System.Threading.Tasks;
using System;
namespace ValueTaskPelda
{
internal class ValueTaskDemo
{
// egy egyszerű cache szimulációja
private int? _cache;
public async ValueTask<int> GetValue()
{
if (_cache.HasValue)
{
// ha van érték a cache-ben, visszaadjuk azt
return _cache.Value;
}
// aszinkron művelet szimulálása
await Task.Delay(1000);
_cache = Random.Shared.Next(1, 100);
return _cache.Value;
}
}
internal class Program
{
private static async Task Main(string[] args)
{
ValueTaskDemo demo = new ValueTaskDemo();
// elsÅ‘ hÃvás, nincs cache, aszinkron művelet
int value = await demo.GetValue();
Console.WriteLine(value);
// második hÃvás, van cache, azonnal visszaadja az értéket
value = await demo.GetValue();
Console.WriteLine(value);
}
}
}
A program kimenete valami hasonló lesz:
74
74
Mint látható, a használatában nincs különbség. Ugyanúgy az await kulcsszóval várakozhatunk a végrehajtására. Lényegében ez egy optimalizációs eszköz, ami specifikus körülmények között javÃtja a teljesÃtményt és nem egy univerzális csodafegyver. A legtöbb esetben a Task alkalmazása jobb választás.
Mikor érdemes használni
- Akkor, ha olyan metódusunk van, amit gyakran meghÃvnak és minden egyes memóriafoglalás jelentÅ‘s teljesÃtményvesztést okozhat.
- Akkor, ha az aszinkron műveletünk nagy valószÃnűséggel szinkron módon is befejezhetÅ‘, mint ahogy a példában láttuk.
- Akkor, ha a memóriahasználat kritikus tényező az alkalmazásunkban.
Mikor nem érdemes
- Egyszerű, aszinkron metódusok esetén, amelyek nem hÃvódnak meg gyakran.
- Ha több helyen is await-tel meg kell várni ugyanazt a műveletet, vagy a Task-hoz több fogyasztó is társul.
- Hosszú ideig, a háttérben futó feladatok esetén, ahol a memóriafoglalás elhanyagolható a futási időhöz képest.