CodeGym /Kurslar /C# SELF /Performansın optimallaşdırılması:

Performansın optimallaşdırılması: ValueTask

C# SELF
Səviyyə , Dərs
Mövcuddur

1. Giriş

Kodun içinə girməzdən əvvəl sadə şəkildə başa düşək. Yadda saxlayın ki, .NET-də obyekt yaratmaq (özəlliklə task yaratmaq!) yaddaşa və bir az CPU vaxtına başa gəlir. İndi təsəvvür edin asinxron API-ni ki, halların yarısında nəticəni dərhal qaytarır (məsələn, cache-dən götürür), digər yarısında isə verilənlər bazasına müraciət edir və əməliyyat həqiqətən asinxron olur və Task tələb edir. Bu tez-tez rast gəlinir — məsələn, fayl cache-lərində, şəbəkə API-lərində, obyekt hovuzlarında və digər "asinxron, amma bəzən dərhal" ssenarilərdə.

Əgər biz hər zaman Task qaytaracağıqsa, hətta nəticə dərhal olduğunda əlavə obyektlər yaratmalı olacağıq. Bəs əgər nəticə artıq hazırdırsa və biz Task qaytarmayaq? Elə bu məqsədlə ValueTask yaranıb.

Fakt: standart Task.CompletedTaskTask.FromResult(…) iş vaxtını qənaət edə bilər, amma ümumi obyekt yaradır ki, yüksək yüklü ssenarilərdə ideal olmaya bilər.

ValueTask nədir

ValueTask xüsusi bir struktur-qablaşdırıcıdır, o ya artıq hazır olan nəticəni, ya da (əgər əməliyyat həqiqətən asinxron-dursa) özü task-ı təmsil edə bilər. Sadə desək, bu "paket" ya sadə nəticə, ya da Task-a istinad saxlayır.

İki əsas növ var:

  • ValueTask — "mənasız" (heç bir dəyər qaytarmır, Task kimi)
  • ValueTask<TResult> — dəyər üçün qablaşdırıcı (Task<TResult>-ın analoqu)

TaskValueTask müqayisəsi

Tip Alokasiyaların sayı Senkron şəkildə tamamlanma Adətən istifadəsi
Task
Bir (heap) Bəli/Xeyr Demək olar ki, həmişə
ValueTask
Sıfır/bir Bəli/Xeyr Optimallaşdırma
ValueTask<T>
Sıfır/bir Bəli/Xeyr Optimallaşdırma

Qayda: nə zaman ValueTask istifadə etməli

Qızıl qayda var: əgər adi asinxron metod yazırsınızsa və nəticə həmişə async-əməliyyat tamamlandıqdan sonra gəlirsə, Task istifadə edin. Bu sadədir, təhlükəsizdir və hamıya aydındır.

ValueTask istifadə etmək düzgündür, əgər:

  • Nəticə sinxron şəkildə əldə oluna bilər (məsələn, cache-dən, hovuzdan, yaddaşdan) və əlavə alokasiyalardan qənaət etmək istəyirsiniz.
  • Asinxron əməliyyat nadir hallarda baş verir (əks halda daxili mürəkkəblik və struktur surətlənməsi üzündən fayda itə bilər).

Diqqət! Əgər siz həmişə asinxron nəticə qaytarırsınızsa — sərbəst Task istifadə edin. Əgər API-nizi tez-tez dərhal gələn nəticələr üçün "super-optimallaşdırmaq" istəyirsinizsə — ValueTask.

2. Sinxron yoxsa asinxron nəticə

İstifadəçi adına görə axtaran funksiya düşünək. Əgər istifadəçi cache-dədirsə — onu dərhal qaytarırıq; yoxdursa — bazadan asinxron yükləyirik:


// Bizim istifadəçi modelimiz
public class User
{
    public string Name { get; set; }
}

// Bizim cache (çox sadə)
private readonly Dictionary<string, User> _localCache = new();

public async ValueTask<User> FindUserAsync(string name)
{
    // Lokal cache-i yoxlayırıq
    if (_localCache.TryGetValue(name, out var user))
    {
        // Nəticə dərhal — task alokasiyası yoxdur!
        return user;
    }

    // Burada guya uzun asinxron əməliyyat (məsələn, bazadan)
    user = await LoadUserFromDbAsync(name);
    // Gələcək üçün cache-ə qoyuruq
    _localCache[name] = user;
    return user;
}

private async Task<User> LoadUserFromDbAsync(string name)
{
    // Gecikməni simulyasiya edirik
    await Task.Delay(500);
    return new User { Name = name };
}

Vacib: Cache-hit halında biz adi nəticə qaytarırıq — heç bir task alokasiyası yoxdur! Yalnız nəticəni "çıxarmağa" məcbur olduğumuzda həqiqi asinxron task yaradılır.

ValueTask içində necə dayanır

ValueTask-ın içində ya hazır dəyər, ya da Task-a istinad ola bilər:

ValueTask result = ValueTask.CompletedTask;
ValueTask<int> valueResult = new ValueTask<int>(42);
ValueTask<int> valueResult2 = new ValueTask<int>(Task.Run(() => 42));

await istifadə edərkən kompilyator özü müəyyən edir: əgər nəticə dərhaldirsə, əlavə obyektlər yaranmayacaq.

awaitValueTask haqqında vacib məqam

async-metodlar await-lə ValueTask-ı yaxşı dostdur:

public async ValueTask PingAsync()
{
    // ...
    await Task.Delay(10);
}

Amma əgər siz ValueTask nümunəsini saxlayıb sonra gözləməyə qərar verirsinizsə, unutmayın: eyni nümunəyə bir dəfədən artıq await etmək qadağandır. Task bir neçə dəfə gözlənilə bilər, amma ValueTask yox.

Xəta: ValueTask üzərində təkrar await

ValueTask<int> task = ComputeAsync();

// Bu OK-dır
int a = await task;

// Bu səhvdir! Eyni ValueTask nümunəsində ikinci await icazəli deyil:
// int b = await task; // OLMAZ!

3. Konsol tətbiqimizin bir hissəsini yenidən yazaq

Tutaq ki, indi kiçik bir kitab oxuyucusu hazırlayırıq: bəzi mətnlər artıq cache-də yüklənib, qalanı isə asinxron olaraq yüklənir. Kitabın ilk sətrini ValueTask ilə optimallaşdırılmış şəkildə alaq:

private readonly Dictionary<string, string> _bookCache = new();

public async ValueTask<string> GetFirstLineOfBookAsync(string title)
{
    if (_bookCache.TryGetValue(title, out var bookText))
    {
        var firstLine = bookText.Split('\n')[0];
        return firstLine;
    }

    // Güman edək ki, kitabın asinxron yüklənməsi var
    var downloadedBook = await DownloadBookTextAsync(title);
    _bookCache[title] = downloadedBook;
    return downloadedBook.Split('\n')[0];
}

private async Task<string> DownloadBookTextAsync(string title)
{
    // Gecikməni simulyasiya edirik (məsələn, internetdən yükləmə)
    await Task.Delay(1000);
    return $"Book: {title}\nThis is the first line.\nSecond line...";
}

Beləcə ValueTask kitab artıq cache-də olduqda task yaratmaqdan qənaət edir.

4. Faydalı nüanslar

Marker: nə vaxt ValueTask istifadə etməmək lazımdır

Əgər sizin API həmişə asinxrondursa (məsələn, hər zaman şəbəkə ilə danışılır) — Task istifadə edin, bu daha sadə və təhlükəsizdir.

ValueTaskTask-a və əksinə necə çevirmək

Bəzən ValueTask bir API-ə sığmır, ona Task lazımdır. Bu halda .AsTask() istifadə edin:

ValueTask<int> valueTask = ComputeAsync();
Task<int> task = valueTask.AsTask();

Əgər Task-dan ValueTask-a keçmək lazımdırsa — sadəcə ValueTask yaradın:

Task<int> task = ComputeAsyncTask();
ValueTask<int> valueTask = new ValueTask<int>(task);

ValueTask vs Task müqayisəsi

Xüsusiyyət
Task
ValueTask
Təkrar await İcazəlidir İcazə verilmir
Sürətli cavablarda alokasiyalar Var Yox (əgər nəticə dərhâldirsə)
İnterfeys uyğunluğu Geniş dəstək Əlavə qablaşdırma tələb edə bilər
Tətbiq sahəsi Hər yerdə Yalnız optimallaşdırma üçün
Pooling (Pool) Ümumi pool-dan istifadə edir Yox, strukturdu
Sadelik Sadə Daha mürəkkəb

Real həyatda ValueTask istifadəsi

public async ValueTask<string> GetMessageAsync(int id)
{
    if (_messageCache.TryGetValue(id, out var value))
        return value;

    var result = await LoadMessageFromDbAsync(id);
    _messageCache[id] = result;
    return result;
}

Müsahibədə tətbiqi: Əgər sizdən soruşsalar asinxron API-nin performansını cache ilə necə daha da yaxşılaşdırmaq olar — ValueTask-dan danışın.

LINQ və IAsyncEnumerable<T> ilə uyğunluq nümunələri

Əgər ValueTask-ı asinxron LINQ (IAsyncEnumerable<T>) ilə istifadə etmək istəyirsinizsə, .NET bunu dəstəkləyir (məsələn, ToListAsync metodu):

public async ValueTask<List<int>> GetPrimeNumbersAsync()
{
    // Simulyasiya: bəzi ədədlər artıq hesablanıb, bəziləri isə asinxrondur
    // ... (nümunə qısaldılıb)
    return new List<int> { 2, 3, 5, 7, 11 };
}

5. Nəticələr və implementasiya xüsusiyyətləri

  1. Əhəmiyyətli vaxtlarda nəticəniz dərhal hazırdırsa (məsələn, cache-dən), optimallaşdırma üçün ValueTask istifadə edin.
  2. ValueTask-ı sadəcə belə istifadə etməyin — bu kodu mürəkkəbləşdirə və səhvlərə yol aça bilər.
  3. ValueTask-ı bir neçə dəfə await etməyin. Lazımdırsa onu Task-a çevirin.
  4. Task ilə uyğunluq .AsTask() vasitəsilədir.
  5. Yadda saxlayın ki, ValueTaskstructdır və surətlənmə zamanı fərqli davranış göstərə bilər.

6. ValueTask ilə işləyərkən tipik səhvlər

Səhv №1: eyni ValueTask nümunəsində təkrar await. ValueTask struktur olduğu üçün onu iki dəfə await etmək olmaz. İkinci await istisnaya və ya səhv işə gətirə bilər.

Səhv №2: uyğunluq yoxlanmadan ValueTask istifadə etmək. Bəzi üçüncü tərəf API-lər Task tələb edir, birbaşa ValueTask ötürülməsi problem yarada bilər.

Səhv №3: ehtiyac olmadan ValueTask istifadə etmək. Əgər nəticə həmişə asinxrondursa, ValueTask kodu çətinləşdirir və fayda vermir.

Səhv №4: struct kimi ValueTask-ın surətlənməsi. Surətlənmiş struktur await zamanı gözlənilməz davranış göstərə bilər, buna görə orijinal ilə işləmək vacibdir.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION