1. Giriş
Siz artıq tanışsınız async və await-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: IDisposable və using 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> və 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?
- IAsyncEnumerable<T>: GetAsyncEnumerator(CancellationToken cancellationToken) metodu olan interfeys. Ləğv tokeni vacibdir!
- 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.
- await foreach: IAsyncEnumerable<T> üzərində rahat iterasiya edir. Kompilator özü MoveNextAsync() və 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ə IAsyncDisposable və await 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.
GO TO FULL VERSION