CodeGym /Kurslar /C# SELF /Asinxron verilən axınları

Asinxron verilən axınları

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

1. Giriş

Siz artıq tanışsınız asyncawait-lə. Bunlar "yalnız bir" asinxron əməliyyatlar üçün, məsələn bir faylın yüklənməsi üçün, əla işləyir. Amma nə olacaq, əgər verilənlər axınla gəlirsə, yaxud resurs asinxron təmizləmə tələb edirsə?

Asinxron "kolleksiyalar" problemi: Tutaq ki, verilənlər bazasından milyonlarla yazı almaq lazımdır. Əgər metod Task<List<T>> qaytarırsa, siz bütün verilənlər yaddaşa yüklənənə qədər gözləyirsiniz. Bu effektiv deyil və gecikmələr yaradır. Sinxron IEnumerable<T> də uyğun deyil, əgər hər elementi asinxron şəkildə əldə etmək lazımdırsa.

Asinxron azad etmə problemi: IDisposableusing sinxron təmizləmə üçün yaxşıdır. Amma şəbəkə bağlantısını bağlamaq və ya buferləri fayla yazmaq asinxron olduqda nə etməli? Sinxron Dispose() daxilində await istifadə etmək olmaz, bu isə thread-in bloklanmasına və ya yanlış təmizləməyə gətirib çıxara bilər.

Bu problemləri həll etmək üçün IAsyncEnumerable<T>IAsyncDisposable təqdim edildi.

2. Asinxron verilən axınları

IAsyncEnumerable<T>IEnumerable<T>-in asinxron analoqudur. O, elementləri ardıcıl olaraq asinxron şəkildə istehsal etməyə imkan verir, bütün verilənlərin hazır olmasını gözləmədən.

Nə vaxt lazımdır?

  • Böyük faylları sətir-sətir asinxron oxumaq: məsələn, gigabaytlıq loglar.
  • Şəbəkədən və ya verilənlər bazasından streaming: API sorğusunun nəticələri hissə-hissə gələndə.
  • Server tərəfli stream API-lərin implementasiyası: məsələn, gRPC Streaming.
  • Hər hansı ssenaridə, əgər verilənlər asinxron şəkildə yaradılır və ya gəlirsə və onlar ardıcıl şəkildə emal olunmalıdır.

Necə işləyir?

  1. IAsyncEnumerable<T>: GetAsyncEnumerator(CancellationToken cancellationToken) metodu olan interfeys. Ləğv tokeni vacibdir!
  2. IAsyncEnumerator<T>: ValueTask<bool> tipində MoveNextAsync() (növbətiyə keçid) və Current (cari element) olan interfeys. O, həmçinin IAsyncDisposable-dən miras alır.
  3. await foreach: IAsyncEnumerable<T> üzərində rahat iterasiya edir. Kompilator özü MoveNextAsync()Current-u çağırır. Ən önəmlisi, await foreach iteratordan DisposeAsync()-ın çağırılmasını zəmanət edir iterasiya bitdikdə, hətta səhv baş verərsə də.

IAsyncEnumerable<T>-i async yield return ilə yaratmaq

Siz yield return-u async metodu daxilində istifadə edə bilərsiniz, hansı ki IAsyncEnumerable<T> qaytarır. Bu, asinxron generatorlar yaratmağa imkan verir. Metodunuz await istifadə edərək generasiyanı dayandıra və asinxron əməliyyatın bitməsini gözləyərək sonra davam edə bilər.

Nümunə: Sadə asinxron generator


async IAsyncEnumerable<int> GenerateNumbersAsync()
{
    for (int i = 0; i < 3; i++)
    {
        Console.WriteLine($"Generiruyu: {i}");
        await Task.Delay(100); // İmitasiya asinxron iş
        yield return i; 
    }
}

// İstifadə:
async Task ConsumeAsyncNumbers()
{
    await foreach (var number in GenerateNumbersAsync())
    {
        Console.WriteLine($"Alındı: {number}");
    }
}
// Çağıra bilərsiniz: await ConsumeAsyncNumbers();

Nümunə: Faylı sətir-sətir asinxron oxumaq


async IAsyncEnumerable<string> ReadFileLinesAsync(string filePath)
{
    using var reader = new StreamReader(filePath); // 'using' burada (StreamReader IAsyncDisposable implementasiya edə bilər)
    string? line;
    while ((line = await reader.ReadLineAsync()) != null) 
    {
        yield return line;
    }
}

// İstifadə:
async Task ProcessFileAsync()
{
    await File.WriteAllLinesAsync("data.txt", new[] { "Stroka 1", "Stroka 2", "Stroka 3" });
    await foreach (var line in ReadFileLinesAsync("data.txt")) 
    {
        Console.WriteLine($"Emal olundu sətir: {line}");
    }
}
// Çağıra bilərsiniz: await ProcessFileAsync();

Nümunə: Ləğv edilə bilən asinxron generator (CancellationToken)


async IAsyncEnumerable<int> GetCancelableSequence(
    [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken token = default)
{
    for (int i = 0; i < 10; i++)
    {
        token.ThrowIfCancellationRequested(); // Ləğvi yoxlayırıq
        await Task.Delay(200, token); // Task.Delay də token vasitəsilə ləğvi dəstəkləyir
        yield return i;
    }
}

// İstifadə:
async Task ConsumeAndCancel()
{
    var cts = new CancellationTokenSource(500); // 500ms sonra ləğv
    try
    {
        await foreach (var num in GetCancelableSequence(cts.Token))
        {
            Console.WriteLine($"Alındı: {num}");
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Generasiya ləğv edildi!");
    }
}
// Çağıra bilərsiniz: await ConsumeAndCancel();

[EnumeratorCancellation] atributu CancellationToken-i asinxron generatora ötürməyə imkan verir. Bu, çağıran kod CancellationTokenSource vasitəsilə ləğv tələb etdikdə iterasiyanı dayandırmağa şərait yaradır. Bu atribut olmadan token avtomatik olaraq GetAsyncEnumerator-ə ötürülməyəcək.

3. Asinxron resurs idarəetməsi

Sinxron IDisposable problemi

Dispose() metodu IDisposable-də sinxrondur (void Dispose()). Onun daxilində await istifadə etmək olmaz. Əgər DB bağlantısını bağlamaq və ya fayla buferləri flush etmək uzun və asinxron əməliyyatdırsa, sinxron Dispose() thread-i bloklayacaq, bu da asinxron tətbiqlər üçün pisdir.

Həll: IAsyncDisposable

IAsyncDisposable bu problemi həll edir. O, tək bir metod ehtiva edir: ValueTask DisposeAsync() — asinxron təmizləmə üçün metod.

await using

Bu using-in asinxron analoqudur. O, IAsyncDisposable implementasiya edən obyektlər üçün nəzərdə tutulub.

  • await using blok bitdikdə və ya istisna baş verdikdə DisposeAsync()-ın çağırılmasını zəmanət edir.
  • Resursları düzgün asinxron şəkildə azad etməyə imkan verir və bloklanmaları aradan qaldırır.

Nümunə: Sadə IAsyncDisposableawait using


class MyAsyncResource : IAsyncDisposable
{
    public MyAsyncResource() => Console.WriteLine("Resurs açıldı.");
    
    public async ValueTask DisposeAsync()
    {
        Console.WriteLine("Asinxron təmizləməyə başlayıram...");
        await Task.Delay(200); // İmitasiya asinxron təmizləmə
        Console.WriteLine("Asinxron təmizləmə tamamlandı.");
    }
}

// await using istifadəsi
async Task UseAndDisposeResource()
{
    await using var resource = new MyAsyncResource(); 
    Console.WriteLine("Resurs istifadə olunur...");
} // Burada avtomatik olaraq resource.DisposeAsync() çağırılır

// Çağıra bilərsiniz: await UseAndDisposeResource();

Nümunə: Bir neçə await using blok


async Task UseMultipleResources()
{
    await using var res1 = new MyAsyncResource();
    await using var res2 = new MyAsyncResource();
    Console.WriteLine("Hər iki resurs istifadə olunur...");
} // resurslar LIFO ardıcıllığında azad olunur: əvvəlcə res2.DisposeAsync(), sonra res1.DisposeAsync() çağırılır.
// Çağıra bilərsiniz: await UseMultipleResources();

IAsyncEnumerable<T>-in IAsyncDisposable-lə uyğunluğu

Vacibdir: IAsyncEnumerator<T> (hansı ki await foreach tərəfindən istifadə olunur) özü IAsyncDisposable-dən miras alır. Bu o deməkdir ki, əgər sizin asinxron generator resurslardan istifadə edirsə (məsələn, yuxarıdakı nümunədə StreamReader), hansı ki asinxron şəkildə azad edilə bilər, await foreach bunun qayğısına qalacaq. O, iterasiya bitdiyi zaman iteratorda DisposeAsync()-ı çağıracaq.

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