1. Giriş
Təsəvvür edin ki, siz — orkestrin dirijorusunuz (sizin tətbiqiniz). Hər dəfə skripkaçı skripkasını sazlayanda (uzun I/O), əgər siz onun bitməsini gözləyirsinizsə və sonra digər alətlərə keçirsinizsə, bütün orkestr dayanacaq. Amma əgər skripkaçı deyirsə: "Mən sazlanacağam, siz isə davam edin, mən hazır olanda işarə verəcəyəm", — budur asinxronluq!
C# və .NET 9 dünyasında belə "donmadan çoxtapşırıq" üçün xüsusi alətlər var. Bugünkü qəhrəmanlar — Read və Write metodlarının asinxron versiyaları: ReadAsync və WriteAsync.
Onlar sizə oxuma və ya yazma əməliyyatını başladıb dərhal cari icra axınını "buraxmağa" imkan verir ki, o başqa işlərlə məşğul olsun. I/O əməliyyatı bitəndə (məsələn, məlumat diskdən oxundu və ya ora yazıldı) kodunuz "oyanacaq" və dayandığı yerdən davam edəcək.
Bu metodlardan istifadə etmək üçün bizə iki sehrli söz lazımdır, onlar C#-a 2012-ci ildə 5.0 versiyası ilə gəlmişdi (indi, C# 14-də, onlar artıq ev şəraitindədir!):
- async: Bu metodun modifikatorudur, kompilyatora demək üçün əlavə edirsiniz: "Bu metodun içində asinxron əməliyyatlar ola bilər və mən await istifadə edəcəyəm".
- await: Asinxron əməliyyatdan əvvəl istifadə etdiyiniz operator. O deməkdir: "Bu əməliyyatı başlat, amma burda onun bitməsini gözləmə. Çağırıcıya nəzarəti qaytar və əməliyyat hazır olanda geri dön".
Əgər bu anlayışlar hələ bir az dumanlı görünürsə, narahat olmayın. Bizdə async və await-ə həsr olunmuş ayrıca bölmə olacaq (Səviyyə 58), orada bütün incə detallarını açacağıq. İndi əsas anlayın ki, onlar əsas axını "bloklamamağa" kömək edir.
2. ReadAsync: tələsmədən oxumaq
ReadAsync metodu axından asinxron şəkildə məlumat oxumağa imkan verir. Baytların diskdən oxunmasını gözləmək əvəzinə oxumağı başlatırsınız və dərhal digər işlərə keçirsiniz.
Onun əsas signaturası bu cür görünür:
public virtual ValueTask<int> ReadAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken = default
)
Və ya müasir C# (və .NET 9) üçün daha çox istifadə ediləni, Memory<byte> ilə:
public virtual ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default
)
Parametrləri anlayaq:
- buffer: Məlumatların oxunacağı byte massivi (və ya Memory<byte>). Buferlərin optimizasiyası haqqında danışmışdıq — eyni prinsip burada də işləyir, amma asinxron əməliyyatlar üçün.
- offset: buffer-də oxunan baytların yazılmağa başlayacağı yer.
- count: Oxunacaq maksimum bayt sayı.
- CancellationToken cancellationToken: Əməliyyatı ləğv etmək üçün faydalı parametr (məsələn, istifadəçi tətbiqi bağladı və ya "Ləğv et" düyməsini sıxdı).
- ValueTask<int>: Əməliyyat bitdikdə geri qaytarılacaq tam ədəd (int) — oxunan baytların sayı. ValueTask nəticənin sinxron və ya asinxron mövcud ola biləcəyi senarilər üçün optimizə olunmuş Task alternatividir.
Nümunə 1: Faylı asinxron oxumaq
Güman edək böyük bir mətn faylımiz var və onu əsas axını bloklamadan oxumaq istəyirik. Sadə nümunə belədir:
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
class Program
{
// Faylı oxuyub sətr sayını asinxron hesablayan funksiya
public static async Task<int> CountLinesAsync(string filePath)
{
int lineCount = 0;
// Faylı asinxron açmaq
using FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true);
using StreamReader reader = new StreamReader(fileStream, Encoding.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
lineCount++;
}
return lineCount;
}
static async Task Main()
{
string filename = "bigtext.txt";
int count = await CountLinesAsync(filename);
Console.WriteLine($"Faylda {filename} sətir sayı: {count}");
}
}
Kod şərhləri:
- useAsync: true parametrinə diqqət yetirin — FileStream-də həqiqi asinxronluq üçün vacibdir.
- await-ı ReadLineAsync-ə tətbiq edirik ki, sətirin oxunmasını gözləyərkən axın bloklanmasın.
- Main metodu artıq asinxrondur (C# 7+ bunu dəstəkləyir).
Əgər bu real qrafik tətbiq olsaydı, fayl oxunarkən (hələ ReadAsync diskdən məlumat gözləyərkən) istifadəçi düymələr sıxa, sürüşdürə və başqa əməliyyatlar edə bilərdi — çünki əsas UI axını bloklanmayıb. Konsol tətbiqində bu o qədər nəzərə çarpmaz, amma prinsip eynidir.
3. WriteAsync: gecikmədən yazmaq
ReadAsync-ə bənzər şəkildə, WriteAsync metodu axına asinxron yazmağa imkan verir. Çoxlu məlumat yazmalı olduğunuzda tətbiqi diskin yazma əməliyyatını gözlətməmək üçün çox faydalıdır.
Əsas signaturalar:
public virtual ValueTask WriteAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken = default
)
Və yazma üçün ReadOnlyMemory<byte> ilə (biz buferi modifikasiya etmirik):
public virtual ValueTask WriteAsync(
ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = default
)
Parametrlər ReadAsync-a oxşardır:
- buffer: Yazılacaq məlumatları saxlayan byte massivi (və ya ReadOnlyMemory<byte>).
- offset: Yazmağa başlanacaq buffer-dəki yerdir.
- count: Yazılacaq baytların sayı.
- CancellationToken cancellationToken: Əməliyyatı ləğv etmək üçün.
- ValueTask: Heç bir dəyər qaytarmır, çünki yazılan baytların sayı count ilə müəyyən edilir.
Nümunə 2: Faylı asinxron yazmaq
İndi fayla nəsə yazaq — yenə asinxron şəkildə.
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
class Program
{
public static async Task WriteTestAsync(string filePath)
{
using FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true);
using StreamWriter writer = new StreamWriter(fs, Encoding.UTF8);
for (int i = 0; i < 10000; i++)
{
await writer.WriteLineAsync($"Sətir nömrə {i}");
}
}
static async Task Main()
{
string filename = "testout.txt";
await WriteTestAsync(filename);
Console.WriteLine($"{filename} yazılması tamamlandı.");
}
}
Burada döngü 10000 sətir yazır və əsas axın bloklanmır: əgər bu GUI tətbiqi olsa idi, interfeys "donmazdı".
async və await- sayəsində konsol tətbiqimiz indi faylın surətini çıxararkən də istifadəçi girişinə (bu nümunədə Enter düyməsini basmaqla ləğv etmə) reaksiya verə bilər. Bu C#-da müasir, performanslı və reaktiv tətbiqlərin yazılmasının əsas prinsiplərindən biridir.
4. Faydalı nüanslar
Vizualizasiya: asinxron oxuma/yazma necə işləyir
┌───────────────────┐ Start Async Read ┌────────────────────────────────┐
│Sizin kod (UI/loqika)│ ─────────────────────→ │ OS/E/S: asinxron əməliyyat │
└─────┬─────────────┘ └───────┬────────────────────────┘
│(başqa işlərlə məşğul olur) │(faylı oxuyur, disk gözləyir)
│<────────────────────────────────────────→│
└─ Task üçün gözləyir, nəticəni alır ←──────┘
Təxmini sxem: disk yavaş işləyərkən kod başqa işlərlə məşğul ola bilər. Məlumat lazım olanda isə icra Task-ın nəticəsini gözləyir.
Praktik tətbiq: bu nə vaxt lazımdır?
- Desktop tətbiqlər: proqramınız böyük fayllar, loglar, verilənlər bazası faylları, video oxuyub-yazırsa, asinxronluq mütləqdir. Hətta güclü maşında belə istifadəçi şəbəkə üzərindən faylı açarsa sürət "tısbağa" ola bilər.
- Backend və ya veb tətbiqlər: serverə eyni anda onlarla və ya minlərlə istifadəçi müraciət edə bilər. Hər axın faylı oxuyanda bloklanarsa — performans və əlaqə problemləri qaçılmazdır (məsələn, 502 Bad Gateway kimi).
- Mobil tətbiqlər: fayl açmaq və ya yazmaq vaxt aparırsa — istifadəçi ləngiməni hiss edəcək. Asinxronluq istifadə edin!
- Kütləvi fayl emalı: arxivləşdirmə, parsing, analiz kimi fayl kolleksiyaları ilə işləyən tətbiqlər asinxron I/O-dan qazanar.
Sinxron vs Asinxron oxuma/yazma
| Metod | Axını bloklayır? | İcra asanlığı? | Yaxşı performans? | UI/server üçün rahatlıq |
|---|---|---|---|---|
| Sinxron (Read/Write) | Bəli | Bəli | Xeyr | Xeyr |
| Asinxron (ReadAsync) | Yox | Dərinə getdikdə asandır | Bəli | Bəli |
5. Nüanslar və ən yaxşı təcrübələr
Buferləşdirmə hələ də vacibdir: ReadAsync və WriteAsync olsa da, baytı-bayt oxuma/yazma hələ də çox səmərəsizdir. Asinxronluq axını bloklamır, amma hər baytı sürətləndirmir. Yaxşı başlanğıc bufer ölçüləri — 4096-8192 bayt; böyük fayllar üçün 65536 və ya 131072 sınamaq məntiqlidir.
"Asinxronluq hər yerdə olmalıdır" (Async All The Way Down): Bir yerdə async/await istifadə edirsinizsə, adətən bunu çağırış zəncirinin bütün səviyyələrinə yaymaq lazımdır: C asinxron əməliyyat edirsə — C async Task olmalıdır, B də async Task, A isə async Task. Əks halda UI tətbiqlərdə bloklanma və hətta deadlock-lar yarana bilər.
İstisnaların işlənməsi: Asinxron koddə adi try-catch istifadə edin. Tez-tez OperationCanceledException və IOException ilə rastlaşacaqsınız — onları açıq şəkildə emal edin.
Resursların azad edilməsi (await using): Stream və digər IDisposable obyektləri düzgün şəkildə azad edin. Əgər tip IAsyncDisposable həyata keçirirsə, await using DisposeAsync()-ı çağıracaq; yalnız IDisposable varsa — Dispose() çağrılacaq.
Alt qatlarda nə baş verir (qısa): await zamanı kompilyator metodu sonlu avtomata çevirir: əməliyyat başlayır, metod "pauzaya" qoyulur, nəzarət çağırana qayıdır. Nəticə hazır olduqda SynchronizationContext (UI-da) və ya ThreadPool (konsolda/servərdə) icranı saxlandığı yerdən davam etdirir. Bu bir tək ipliklə çoxlu "dayandırılmış" tapşırıqları bloklamadan xidmət etməyə imkan verir.
Nəticə olaraq, async və await ilə asinxron proqramlaşdırma reaktiv və şkalalanabilən tətbiqlər yaratmaq üçün güclü vasitədir. Bu kodun sistem resurslarını effektiv istifadə etməsinə, UI-nin və server axınlarının bloklanmamasına imkan verir. Əvvəlcə özünü qəribə hiss etdirə bilər, amma əmin olun — öyrənməyə dəyər! Gələn mühazirələrdə asinxronluq və paralelliyə daha dərindən baxacağıq. Görüşənədək!
GO TO FULL VERSION