Z C# 7 został wprowadzony nowy typ a konkretniej struktura ValueTask która jest odpowiednikiem klasy Task. Głównym celem tej struktury jest poprawa wydajności metod asynchronicznych w których ścieżka synchroniczna jest dużo częściej wykonywana niż asynchroniczna 🤔 Spójrzmy na poniższy kod:

1
2
3
4
5
6
7
8
9
10
public async Task<IList<HolidayDetailsDto>> GetHolidaysTask()
{
    if (_cachedTaskData == null)
    {
        _cachedTaskData = await _httpClient.GetFromJsonAsync<IList<HolidayDetailsDto>>("https://date.nager.at/api/v2/publicholidays/2022/PL");
    }

    return _cachedTaskData;

}

Kod jest prosty, jeżeli nie mamy danych w cachu to pobieramy je, jeżeli są to od razu je zwracamy. Czyli kod synchroniczny – zwracanie danych z cacha, będzie dużo częściej wykonywany niż kod asynchroniczny – pobieranie danych z jakiegoś serwera. Poniżej kod który używa już ValueTask:

1
2
3
4
5
6
7
8
9
10
public async ValueTask<IList<HolidayDetailsDto>> GetHolidaysValueTask()
{
    if (_cachedValueTaskData == null)
    {
        _cachedValueTaskData = await _httpClient.GetFromJsonAsync<IList<HolidayDetailsDto>>("https://date.nager.at/api/v2/publicholidays/2022/PL");
    }

    return _cachedValueTaskData;

}

Jedyna różnica to tylko zwracany typ. Jak to ma się do wydajności? ⤵️

ValueTask vs Task – wydajność

Za pomocą poniższego kodu porównałem wydajność Task i ValueTask. Wydajność mierzyłem za pomocą BenchmarkDotNet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Benchmark]
public async ValueTask ValueTask()
{
    for (int i = 0; i < 5000; i++)
    {
        var result = await GetHolidaysValueTask();
    }
}

[Benchmark]
public async Task Task()
{
    for (int i = 0; i < 5000; i++)
    {
        var result = await GetHolidaysTask();
    }
}

Wynik: Jak widzimy, szybkość działania metod jest bardzo podobna jednak użycie pamięci (kolumna Allocated) jest dużo mniejsze gdy używamy ValueTask.

Kod powyższego benchmarku znajdziecie tutaj: https://github.com/Carq/PerformanceLab/tree/master/ValueTaskVsTask

Podsumowanie – kiedy używać

ValueTask należy używać gdy w aplikacji korzystamy z metod asynchronicznych w których ścieżka synchroniczna jest dużo częściej wykonywana niż asynchroniczna. Używając ValueTask możemy zaoszczędzić prawie połowę pamięci.

Polecane linki