1. Giriş
Proqramlaşdırmada yeni başlayanlar (və hətta təcrübəli developerlər) çox vaxt iki oxşar, amma əslində fərqli konsepsiyanı qarışdırırlar: çoxsəlislik və asinxronluq. Müsahibələrdə bu sualı verməyi sevirlər ki, insanın fərqi anlayıb-anlamadığını görsünlər, çünki bundan birbaşa asılıdır necə sürətli və cavabdeh kod yazılacaq.
Gəlin başa düşək, nədə hiylə var.
Çoxsəlislik: çox əllə işləmək
Çoxsəlislik — proqramın işini bir neçə thread vasitəsilə təşkil etməkdir. Thread — bu icra xətti, prosessorun proqram təlimatlarını icra etdiyi ayrıca "yol". Bir proses (məsələn, bizim .NET tətbiqimiz) eyni anda bir neçə thread işə sala bilər ki, müxtəlif tapşırıqlar eyni vaxtda icra olunsun.
Həqiqi həyat nümunəsi: Siz — layihə menecerisiniz, müxtəlif tapşırıqları fərqli həmkarlara tapşırırsınız və bu tapşırıqlar hamısı eyni vaxtda görülür. Məsələn, biri hesabat yazır, digəri müştəriyə zəng edir, üçüncü təqdimat hazırlayır.
Çoxsəlisliyin əsas ideyası: tapşırıqlar həqiqətən paralel baş verir (və ya kvazi-paralel, əgər prosessor təkdirsə, kontekstin tez-tez dəyişməsi sayəsində).
Asinxronluq: işsiz dayanmaqdan bacarıq
Asinxronluq — kodu elə təşkil etməkdir ki, proqram uzun müddət tələb edən əməliyyatın (məsələn, internetdən cavab və ya fayl oxuma) bitməsini gözləyərkən başqa işlə məşğul ola bilsin. Asinxron kod mütləq bir neçə thread istifadə etmir! O sadəcə proqramınızın icrasını gözləmə zamanı bloklamır.
Həqiqi həyat nümunəsi: Qəhvə maşınının yanında durub qəhvə damcılarını gözləmək əvəzinə, maşına tapşırıq verirsiniz və özünüz başqa işlərlə (e-mail cavablandırma, xəbərlərə baxma) məşğul olursunuz, qəhvəni hazır olduqda götürürsünüz.
Asinxronluğun əsas ideyası: uzun tapşırıqları gözləyərkən boş dayanmayıb nəsə faydalı etmək.
2. Fərq nədir?
Çox vaxt asinxronluq və çoxsəlislik birlikdə istifadə olunur, və bu qarışıqlığı artırır. Amma əslində bu texnikalar fərqli suallara cavab verir:
- Çoxsəlislik işin real paralelləşdirilməsi üçün lazımdır, mövcud prosessorlardən/çekirəklərdən istifadə edir.
- Asinxronluq resurs gözləməsini (şəbəkə I/O, disk I/O və s.) ağıllı şəkildə təşkil etmək üçün lazımdır, thread-i bloklamadan.
Bunları birləşdirmək olar, amma onlar bir-biri ilə məcburi bağlı deyil.
Qısa vizuallaşdırma
| Çoxsəlislik (Threads) | Asinxronluq (Async) | |
|---|---|---|
| Nə vaxt tətbiq etmək? | Tapşırıq prosessoru "yeyirsə" (CPU-bound: hesablamalar, rendering, massiv işlənməsi) | Tapşırıq bir hadisəni "gözləyirsə" (I/O-bound: şəbəkə, disk, verilənlər bazası) |
| Kod nə edir? | Prosessoru döndərir, bir neçə thread işə salır, real paralelləşdirir | Gözləyərkən ("boş dayanarkən") thread-i azad edir — o başqa işlə məşğul ola bilər |
| Nəyə nəzarət edir? | Eyni vaxtda işləyən threadlərin sayına | Kim indi məşğuldur/boşdur və əməliyyat bitdikdə nə etmək |
| Tipik nümunə | Video sıxışdırma, şəkil renderlənməsi | Fayl yükləmə, HTTP sorğusu serverə |
Nümunə 1: Çoxsəlislik — sürətlə hesablayırıq
Tutaq ki, bizim ağır tapşırığımız var: böyük ədədlərin cəmini hesablamaq.
void ComputeSum(long start, long end)
{
long sum = 0;
for (long i = start; i <= end; i++)
{
sum += i;
}
Console.WriteLine($"Cəmi {start}-dən {end}-ə = {sum}");
}
// Eyni anda üç tapşırıq işə salırıq — hər biri öz hissəsini hesablayır
Thread t1 = new Thread(() => ComputeSum(1, 1000_000_000));
Thread t2 = new Thread(() => ComputeSum(1000_000_001, 2000_000_000));
Thread t3 = new Thread(() => ComputeSum(2000_000_001, 3000_000_000));
t1.Start();
t2.Start();
t3.Start();
// Bütün threadlərin bitməsini gözləyirik
t1.Join();
t2.Join();
t3.Join();
Niyə burada threadlər?
Çünki iş CPU-yönümlüdür. Threadlər prosessoru həqiqətən yükləyir, və əgər çoxnüvəli maşınınız varsa — iş sürətlənəcək.
Нümunə 2: Asinxronluq — serverdən cavab gözləyirik
Şəbəkə — yavaşdır, və biz serverdən cavab gözləyərkən thread "boş" ola bilər.
// Asinxron şəkildə sayfanı yükləyirik, thread bloklanmır
async Task DownloadPageAsync()
{
using HttpClient client = new HttpClient();
string html = await client.GetStringAsync("https://dotnet.microsoft.com/");
Console.WriteLine(html.Length);
}
Niyə burada asinxronluq?
Biz sistemə "Yükləməyə başla" deyirik, və bu zaman thread-i bloklamırıq, məlumat gələndə xəbərdarlıq gözləyirik.
3. Asinxronluq çoxsəlisliksiz: mif yoxsa həqiqət?
Sual: Hər asinxron kod həmişə yeni thread başladırmı?
Cavab: Xeyr! Çox vaxt asinxronluq əlavə thread tələb etmir.
Məsələn, await file.ReadAsync(...) etdikdə, .NET əməliyyatı OS səviyyəsində asinxron şəkildə işə salır, və bu çağırışı edən thread dərhal "boşalır" və thread pool-a qayıdır. Əməliyyat bitdikdə, pool-dan hər hansı bir boş thread tapılacaq və sizin tapşırığın icrasını davam etdirəcək.
- Əgər asinxronluq əvəzinə adi (file.Read(...)) istifadə etsəydiniz — thread sadəcə əməliyyatın bitməsini gözləyərdi və heç nə etməzdi.
- Asinxron kod deyir: "Prosessor, gözləyərkən başqa işlə məşğul ol!"
Vacib nümunə:
// "Thread" bloklanmır, yalnız əməliyyat hazır olana qədər gözləyir
await Task.Delay(1000); // Bir saniyə gözləmək — prosessoru işləmir!
Çoxsəlislik asinxronluqsuz
Bəzən işi paralelləşdirmək məntiqlidir yalnız threadlərlə: ağır hesablamalar, böyük dövrlərin işlənməsi və s. Bu halda asinxronluq hesablamaların özünü sürətləndirmək üçün faydasızdır, çünki prosessor onsuz da 100% yüklənəcək.
Klassika: böyük faylların işlənməsi
// Bu kod həqiqətən prosessoru yükləyir — asinxronluq kömək etməz.
void CalculateHash(string file)
{
byte[] data = File.ReadAllBytes(file); // sinxron!
// Hash hesablayırıq...
}
Sürətləndirmək istəyirsinizsə — bir neçə thread işə salın, hər biri öz faylı üzərində işləsin.
4. Bu sizin tətbiqinizdə necə görünür?
Asinxron əməliyyatlar (await)
Tədris tətbiqimizdə verilənlərin asinxron yüklənməsini əlavə etmək olar. Məsələn, valyuta kurslarını və ya hava proqnozunu soruşursunuzsa — bunu asinxron etmək daha yaxşıdır.
async Task GetWeatherAsync(string city)
{
using HttpClient client = new HttpClient();
string json = await client.GetStringAsync($"https://api.weather.com/{city}");
// Cavab gələndə işləməyə davam edirik
Console.WriteLine($"Hava {city}-də: {json}");
}
Gizli tərəfdə nə baş verir?
await çağırışı metodunuzu iki hissəyə "kəsir":
- Asinxron əməliyyatı çağırdıq — OS thread-i "azad olur" və başqa işlərlə məşğul ola bilər.
- Məlumatlar alındıqda — metod icrasına pool-dan alınan hər hansı boş thread üzərindən davam edir.
Çoxsəlislik mürəkkəb hesablamalar üçün
Kalkulyator nümunəmizdə (güman edək ki, böyük massivlər üzərində işləməyə başladı) — burada hesablamaları ayrı threadlərdə işə salmaq məntiqlidir.
// Böyük tapşırığı kiçik hissələrə bölürük, hər biri öz hissəsini hesablayır
List<Thread> threads = new List<Thread>();
for (int i = 0; i < 4; i++)
{
int rangeStart = i * 1000000;
int rangeEnd = (i + 1) * 1000000 - 1;
Thread t = new Thread(() => ComputeSum(rangeStart, rangeEnd));
threads.Add(t);
t.Start();
}
// Bütün threadlərin bitməsini gözləyirik
foreach (Thread t in threads) t.Join();
5. Asinxronluqla işləyərkən tipik səhvlər və nüanslar
Səhv №1: async-i "sürət üçün" istifadə etmək.
Çox yayılmış yalnış fikir — async-in kodu sürətləndirdiyini düşünmək. Belə deyil. Asinxronluq — cavabdehlik (responsiveness) üçündür, sürət üçün deyil.
Əgər tapşırıq CPU-bound-disə (prosessor yüklənməsi) — asinxronluq onu daha sürətli etməyəcək.
Əgər tapşırıq I/O-bound-disə (şəbəkə, disk işi) — asinxronluq faydalıdır, çünki thread boş-dayanmır və başqa iş görə bilər.
Səhv №2: Wait() və Result ilə threadləri bloklamaq.
Asinxron kodda Wait() və ya Result çağırmaq qətiyyən olmaz. Bu demək olar ki, həmişə threadlərin bloklanmasına və deadlock-lara gətirib çıxarır.
// Pis! Thread-i bloklayacaq və problemlərə səbəb olacaq
var result = GetDataAsync().Result;
async Task<string> GetDataAsync() { /* ... */ return "data"; }
Düzgün yanaşma — await istifadə etmək və thread-i bloklamamaqdır.
Səhv №3: asinxronluq və UI.
Qrafik tətbiqlərdə (WPF, WinForms) əsas problem — UI-thread-i dondurmamaqdır. Əsas thread-də uzun və ya bloklayıcı əməliyyat işi aparılsa, bütün tətbiq "donacaq". Asinxronluq bu problemi həll edir: ağır iş fon-da icra olunur, və interfeys cavabdeh qalır.
Səhv №4: asinxron metodların adlandırma konvensiyasının olmaması.
Asinxron metodlara Async suffix-i əlavə etməsəniz, hansı metodun sinxron, hansının asinxron olduğunu qarışdırmaq asandır. Bu təsadüfi bloklamalara və çağırış səhvlərinə gətirib çıxarır. Həmişə asinxron metodları sonunda Async ilə adlandırın.
GO TO FULL VERSION