1. Giriş
Bloklayan çağırış — bu, hər hansı bir çağırışdır ki, müəyyən əməliyyat tamamlanana qədər thread-in icrasını “bloklayır”. Adətən bunlar:
- Məlumat oxuma və ya yazma (məsələn, diskdən).
- Uzun müddətli sorğu verilənlər bazasına və ya şəbəkəyə.
- "Yavaş" işləyən üçüncü tərəf kitabxanasının çağırışı.
Belə anlarda thread sadəcə dayanır, yerində kiçik-kiçik hərəkətlər edir və gözləyir: “Nə olur, cavab nə vaxt olacaq?”
Həyatdan nümunə
// Sizin əsas məntiq
Console.WriteLine("Serverdən cavab gözləyirəm...");
string response = CallServer(); // burada hər şey ilişib qalır!
Console.WriteLine("Server cavab verdi: " + response);
Server cavab verənə qədər sizin thread "ilişir" — və heç nə edə bilmir!
Proqramlarda bu necə görünür
Müxtəlif tip proqramlarda bu özünü fərqli şəkildə göstərir. Konsol proqramında elə görünür ki, proqram sadəcə “donub” — kursor yanıb-sönməyi dayandırır və heç bir reaksiya görünmür. Qrafik interfeysdə, məsələn WinForms və ya WPF-də pəncərə birdən-birə cavab verməyi dayandırır, tanış “not responding” görünür və istifadəçi artıq proqramı və sizi lənətləməyə hazırdır. Server tətbiqlərində vəziyyət daha pisdir: bir ilişmiş thread bir istifadəçini itirmək deməkdir.
2. C#-da praktiki bloklayan çağırışlar
Gəlin vəziyyəti əlimizlə “sənəyək”, tədris layihəsi üzərində. Tutaq ki, bizim bir sehrli top tətbiqimiz var. Orada belə bir fraqment ola bilər:
Console.WriteLine("Sehrli top üçün sualınızı daxil edin:");
string question = Console.ReadLine(); // bloklayan çağırış: input gözləyirik!
Console.WriteLine("Cavab üzərində düşünürəm...");
Thread.Sleep(5000); // uzun işləmənin emulyasiyası
Console.WriteLine("Cavab: sabah yenidən cəhd edin!");
- Console.ReadLine() — istifadəçinin daxil etməsini lazımi qədər gözləyir (thread-i bloklayır).
- Thread.Sleep(5000) — süni şəkildə fasilə yaradır, thread-i dondurur.
Sual: Əgər biz sadəcə konsol tətbiqi ilə işləyiriksə, bunun nə pis tərəfi var?
Cavab: Konsol tətbiqində bu o qədər də kritik deyil — istifadəçi özü gözləyir. Amma əgər istifadəçi tək deyilsə? Əgər bu serverdirsə və eyni anda 1000 sorğu gəlirsə? Və ya GUI tətbiqdirsə, istifadəçi interfeysin cavabını gözləyir, sizin filosofik düşüncələrinizi yox.
Nümunə: UI-nın bloklanması
// WinForms-də düymə handler-i:
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Yüklənir...";
DoHeavyWork(); // ağır əməliyyat (məsələn, DB sorğusu)
label1.Text = "Hazır!";
}
Problem: DoHeavyWork işlədiyi müddətcə pəncərə interfeysi yeniləməz, klaviatura və siçan hadisələrinə cavab verməz, yenidən çəkmə olmaz. İstifadəçi pəncərəni bağlamağa çalışsa — Windows "not responding" göstərəcək və vəzifəni dayandırmağı təklif edəcək.
3. Çoxthreadli dünyanın əsas dərdi
Bloklayan çağırışların problemləri
- Resursların israfı. Hər bir thread (.NET-də Thread) kifayət qədər “ağır” obyektdir. OS ona stack üçün yaddaş ayırır (adətən 1 MB), descriptor-lar, sinxronizasiya və s. Əgər thread “passivdirsə” (fayl və ya şəbəkə gözləyirsə), o lazımsız yer tutmuş olur (“heç nə etmədən yaddaş yeyir”).
- Thread məhdudiyyəti. Server tətbiqlərində adətən thread pool mövcuddur. Bütün thread-lər bloklandıqda — yeni sorğular emal olunmur. Məsələn, ASP.NET-də: mövcud bütün thread-lər baza cavabını gözləyirsə, sayt bütün yeni istifadəçilər üçün “donur”.
- İnterfeysin pis reaksiyası. GUI tətbiqlərində UI-thread bloklandıqda pəncərə hətta "bağla" əmrlərinə belə cavab vermir.
- Performansın aşağı düşməsi. Neçə çox thread bloklanarsa, o qədər çox context switch olur, OS-a yüklənmə artır və bütün maşın daha yavaş işləyir.
Bloklayan çağırışların tipik mənbələri
Gəlin baxaq, hansı çağırışlar "sakitcə" thread-i bloklaya bilər:
- Şəbəkə əməliyyatları: API çağırışları, fayl yükləmə, update-lərin yüklənməsi.
- Disk/fayl sistemi: böyük faylları oxuma və ya yazma.
- Verilənlər bazası: uzun SQL sorğuları və ya hətta lokal baza sorğuları.
- İstifadəçi girişi: uzun müddətli ReadLine — kiçik şeydir, amma bloklayır.
- Thread.Sleep, Task.Delay: süni şəkildə thread-i “dondurur”.
Nümunə (saytdan məlumat yükləmə)
using System.Net.Http;
HttpClient client = new HttpClient();
string result = client.GetStringAsync("https://google.com").Result; // bloklayan sinxron çağırış!
Console.WriteLine(result);
Burada nə baş verir? .Result metodu cavabı alana qədər thread-i bloklayır!
4. Çağırışın thread-i blokladığını necə başa düşmək olar?
Hər şey sadədir: əgər metod öz işi bitənə qədər (gözləmə, input, şəbəkə, disk) idarəni qaytarmırsa — bu bloklayan çağırışdır.
- Thread.Sleep, Task.Wait, .Result, .Wait() olan metodlar — bloklayır.
- Asynchronous olmayan oxuma/yazma əməliyyatları olan metodlar da bloklayır.
- “Donmuş” pəncərə və ya ilişmiş server — tez-tez görünən əlamətdir.
Vizual element: Bloklayan çağırışın blok-sxeması
+-------------------+
| Metodun başlanğıcı|
+-------------------+
|
v
+-------------------+
| Bloklayan metodu |
| çağırırıq (məsələn,|
| fayl oxuma) |
+-------------------+
|
v
+-------------------+
| Əməliyyatın bitməsini|
| gözləyirik |
+-------------------+
|
v
+-------------------+
| Metoddan çıxırıq və|
| davam edirik |
+-------------------+
5. Server tətbiqlərində bloklayan çağırışların nə üçün pis olduğuna dair
Moden tətbiqlərin əksəriyyəti — şəbəkə və ya server orientasiyalıdır. Hətta oyun yazırsınızsa, serverdən yükləmələr olacaq. Veb sayt yazırsınızsa, şəbəkə və disk işi mütləq olacaq.
Tutaq ki, serveriniz saniyədə 1000 sorğunu emal edir. Hər sorğuya baza ilə danışmaq lazımdır. Siz belə yazırsınız:
// Veb-handler
string data = db.ReadDataSync(); // Sinxron bloklama!
return new Response(data);
Baza sorğusu gedən zaman thread sadəcə boş oturub gözləyir. Bir sorğu — yaxşı. Amma bir dəstə toplaşanda, bütün thread-lər dolur. Yeni sorğular artıq daxil ola bilmir və sırada qalırlar. Nəticədə server maşından yox, uzun növbədən ibarət bir yerə çevrilir: hamı dayanıb gözləyir.
İllustrasiya: "Sinxron sorğuların emalı"
Sorğu 1: thread götürdü -> bazanı gözlədi -> azad oldu
Sorğu 2: thread götürdü -> bazanı gözlədi -> azad oldu
...
Sorğu 100: boş thread yoxdur, sırada gözləyir...
6. Asinxronluq — bloklayan çağırışların dərmanı
Bloklanmanı aradan qaldırmaq üçün asinxron çağırışlar istifadə olunur. C#-da bunlar sehrli sözlərdir: async və await.
Onlar imkan verir:
- Thread-i bloklamamaq (xüsusən UI və ya server thread-i üçün qiymətli).
- Resursları mənasız yerə tutmamaq.
- GUI-nin cavabverməsini yaxşılaşdırmaq.
- Uzun əməliyyat gedərkən thread-i “tutmamaq”.
Nəzərə alın: biz dərhal hər şeyə asinxron keçmirik, çünki bu, threadlər və bloklanma haqqında anlama tələb edir. İndi bloklayan çağırışların əzabını başa düşdüyünüzə görə, asinxron yanaşma həm beyniniz, həm də istifadəçi üçün həqiqi rahatlıq olacaq.
Cədvəl: Nə bloklayır, nə bloklamır?
| Metod/Çağırış | Thread-i bloklayır | UI/Server üçün uyğundur |
|---|---|---|
|
Bəli | Xeyr |
|
Bəli | Xeyr |
|
Bəli | Xeyr |
|
Bəli | Xeyr |
|
Bəli | Xeyr |
|
Xeyr | Bəli |
|
Xeyr | Bəli |
|
Xeyr | Bəli |
Qeyd: await yalnız asinxron funksiyada işləyir (async Task ...), bu barədə növbəti mühazirələrdə daha ətraflı danışacağıq.
7. Bloklayan çağırışlarla işləyərkən məşhur səhvlər
“UI-ni bloklayıram — istifadəçi hər şeyi lənətləyir”
private void btnLoad_Click(object sender, EventArgs e)
{
var data = BigFileReader.Read(@"C:\huge.dat"); // bloklayan çağırış!
textBox1.Text = data;
}
Böyük fayl oxunana qədər pəncərə “donur”.
“Serverin ilişməsi — müştərilər qaçır”
public IActionResult Download()
{
var content = File.ReadAllBytes("bigfile.zip"); // sinxron, uzun, ASP.NET thread-ini bloklayır!
return File(content, "application/zip");
}
Kiçik serverlər (məsələn, pulsuz hostingdə) belə yanaşma ilə sadəcə “çağa bilər”.
“Asinxron metod + .Wait()/.Result = sinxron ölüm”
public void LoadData()
{
var result = DoAsyncWork().Result; // Thread-i bloklayır!
}
.Result və .Wait() hətta gözəl asinxron kodu adi bloklayan çağırışa çevirir.
GO TO FULL VERSION