CodeGym /Kurslar /C# SELF /Asinxronluq vs. Çoxsəlislik ( async/await və Thread)

Asinxronluq vs. Çoxsəlislik ( async/await və Thread)

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

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.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Sadə asinxron əməliyyat nümunəsi
Sadə asinxron əməliyyat nümunəsi
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION