1. Giriş
Asinxron proqramlaşdırma dünyasında biz "geri dönüşlü tapşırıqvermə" prinsipi üzrə yaşayırıq. Təsəvvür edin: sizə böyük bir faylı yükləmək, gigabaytlarla logları analiz etmək və ya uzaq serverə sorğu göndərmək lazımdır. Dayanıb heykəl kimi gözləmək əvəzinə biz sistemə deyirik: "Bununla məşğul ol, mən isə başqa iş görəcəm. Bitirəndə — mütləq xəbər et!"
Məhz burada səhnəyə Task çıxır — bu yanaşmanın zərif təzahürü. Bu sadəcə texniki abstraksiya deyil, işin icrasını üzərinə götürən və nəticənin itməməsini təmin edən "ağıllı vasitəçi" kimidir.
Task şəxsi köməkçi kimi işləyir: ona vacib işi tapşırırsınız. O baş əyir, işi qeyd edir və deyir: "Sakitcə öz işinizlə məşğul olun, bitirən kimi sizi tapacağam". Və həqiqətən də sizi tapır — ya nəticə ilə, ya da niyə şeyin səhv getdiyini izah etməklə.
Gündəlik analogiya axtarsaq, Task müasir onlayn qeydiyyat sistemini xatırladır: həkimə qeydiyyatdan keçirsiniz, təsdiq alırsınız və növbədə saatlarla oturmağa ehtiyac qalmır. Sistem qəbul vaxtını xatırladır, siz isə bu vaxt ərzində öz həyatınızı yaşaya bilərsiniz.
Sinif Task
Task — .NET-də asinxron proqramlaşdırmanın əsas tikinti blokudur. O gələcəkdə əlçatan olacaq işləyən və ya işləyəcək əməliyyatı təmsil edir. Metod heç nə qaytarmamalıdırsa, sadəcə Task istifadə edirik.
public async Task BackupToCloudAsync()
{
// Backup sehri edir, heç nə qaytarmır
}
Sinif Task<TResult>
Nəticə qaytarmaq lazımdırsa (məsələn, string, ədəd, obyekt…), Task<TResult> istifadə edirik:
public async Task<string> DownloadHtmlAsync(string url)
{
// Səhifəni yükləyir və HTML kodunu qaytarır
return "<html>...</html>";
}
Niyə Task, Thread yox?
Thread özü axını idarə edir (bu çətindir və risklidir), amma Task daha yüksək səviyyəli abstraksiyadır: o thread pool-da icra oluna bilər, I/O əməliyyatları zamanı yeni thread ayırmadan asinxron işləyə bilər və aşağı səviyyəli detallarla məşğul olmağı tələb etmir.
Task belə ifadə etməyə imkan verir: "Mən bunu işə salmaq istəyirəm", necə icra olunacağına isə .NET qərar verir!
2. Task obyektinin quruluşu
Biləcəyiniz Task xüsusiyyətləri və metodları
| Xüsusiyyət / Metod | Təsviri |
|---|---|
|
Tapşırığın cari vəziyyəti |
|
Task<TResult> üçün nəticə (axını bloklayır) |
|
Tapşırıq başa çatıb mı |
|
Tapşırıqda istisna baş veribmi |
|
Tapşırıq ləğv edilibmi |
|
Cari axını bitənə qədər bloklayır (təhlükəli) |
|
Bitişdən sonra başqa bir tapşırığı işə sal |
|
Əgər Task səhv ilə bitdisə, istisnaya giriş |
|
Tapşırığın unikal identifikatoru |
Task ilə asinxron metod necə işləyir
sequenceDiagram
participant Main as Main Axını
participant Task as Task (Fon tapşırıq)
Main->>Task: Zapusk Task.Run(() => ...)
Note right of Task: Fonda icra edilir
(CPU və ya I/O)
alt Tapşırıq başa çatdı
Task->>Main: await bitdi, davam edirik
else Səhv
Task->>Main: await istisna atır
end
3. Tapşırıqların yaradılması və işə salınması: Task necə işləyir
async ilə asinxron metodlar
Ən çox rast gəlinən hal — metodu async kimi elan edib ya Task, ya da Task<TResult> qaytarmaqdır (indi gördüyümüz kimi).
Task.Run: thread pool-da icra
Əgər fon işində (məsələn, böyük hesablamalar və ya video kodlaşdırma) ağır iş görmək lazımdırsa, Task.Run istifadə etmək olar:
Task work = Task.Run(() =>
{
// Ağır hesablamalar — əsas axını bloklamırıq!
Console.WriteLine("Fon hesablamalar başladı...");
Thread.Sleep(2000); // Uzun işin emulyasiyası
Console.WriteLine("Fon hesablamalar bitdi!");
});
Əgər nəticə almaq faydalıdırsa:
Task<int> calculateTask = Task.Run(() =>
{
// Məsələn, ilk 100 ədədin cəmini hesabla
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum;
});
Task.Factory.StartNew
Bu daha aşağı səviyyəli və çevik üsuldur, tapşırığın işə düşməsini incə tənzimləməyə imkan verir (məsələn, scheduler göstərmək, parametrlər ötürmək və s.). Müasir kodda adətən Task.Run tövsiyə olunur — daha sadədir və səhvlərdən qoruyur.
4. Gündəlik tətbiq: bizim kitab kataloqu
Tutaq ki, bizim kitab kataloqu tətbiqimiz var və "bulud" mənbəyindən kitabları yükləmək funksiyası əlavə etməliyik — bu I/O-bound əməliyyat olacaq (yavaş HTTP sorğusu və ya fayldan oxuma).
Gecikməni emulyasiya edərək asinxron kitab yükləyən metod əlavə edək:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
public class BookCatalog
{
public List<Book> Books { get; set; } = new();
public async Task LoadBooksAsync()
{
Console.WriteLine("Kitablar yüklənir...");
await Task.Delay(2000); // Uzun yüklənmənin emulyasiyası (məsələn, HTTP və ya fayl)
Books = new List<Book>
{
new Book { Title = "CLR via C#", Author = "Jeffrey Richter" },
new Book { Title = "C# in Depth", Author = "Jon Skeet" }
};
Console.WriteLine("Kitablar uğurla yükləndi.");
}
}
Main-də asinxron yükləməni çağırırıq (await istifadə edərək):
var catalog = new BookCatalog();
await catalog.LoadBooksAsync();
Console.WriteLine($"Kataloqda {catalog.Books.Count} kitab var.");
Cədvəl: Task yaratma və işə salma əsas üsulları
| Yaratma metodu | Necə istifadə olunur | Nəticə | Tətbiq |
|---|---|---|---|
| async-metod | |
Asinxron əməliyyat | Adətən I/O, rahatlıq |
|
|
Fon tapşırıq | CPU-bound (həcmli hesablamalar) |
|
Task-ı əl ilə yaradır və tamamlayırıq | Proqramçı üçün tam nəzarət | Nadir hallarda, aşağı səviyyəli işlər üçün |
5. Task-in həyat dövrü
Task müxtəlif vəziyyətlərdə ola bilər:
- Created — tapşırıq yaradılıb, amma işə düşməyib (açıq şəkildə işə salınan Task üçün).
- WaitingToRun — pool-da növbəni gözləyir.
- Running — icra olunur.
- WaitingForActivation — işə düşməyi və ya xarici aktivləşdirməni gözləyir.
- RanToCompletion — uğurla tamamlandı.
- Faulted — səhv (istisna) ilə tamamlandı.
- Canceled — ləğv edildi (əgər ləğv dəstəklənirsə).
Diaqram
flowchart LR
Start -->|Tapşırığın işə salınması| Running
Running -->|Uğurla| Completed
Running -->|Səhv| Faulted
Running -->|Ləğv| Canceled
Bunu praktikada yoxlayaq
Task task = Task.Run(() =>
{
Thread.Sleep(1000);
});
Console.WriteLine(task.Status); // Adətən: Running və ya WaitingToRun
await task;
Console.WriteLine(task.Status); // Bitdikdən sonra RanToCompletion
6. Task<TResult>-dən nəticə necə almaq olar?
Task<TResult> — gələcəkdə yaranacaq nəticənin əhatə qabığıdır. Nəticəni gözləmək lazım olduqda await istifadə edirik:
Task<int> sumTask = Task.Run(() =>
{
int sum = 0;
for (int i = 1; i <= 5; i++) sum += i;
return sum;
});
int result = await sumTask;
Console.WriteLine(result); // 15
Əgər await yazmağı unutmusunuzsa, siz Task (vədi) alırsınız, nəticəni yox — bu tipik "asinxron tələ"dir.
Alternativ: Sinxron nəticə almaq (UI-da ETMƏYİN!)
Bəzən (məsələn, testlərdə) nəticəni await olmadan almaq lazım olur. .Result xassəsindən istifadə etmək olar:
int result = sumTask.Result;
Amma əgər Task hələ bitməyibsə, bu kod axını bloklayır, və əgər bu UI-axınıdırsa, tətbiq donacaq! Buna görə: həmişə await-u üstün tutun.
Task və Task<TResult> ilə tipik səhvlər
Task qaytarmayıb, metod void oldu. Əgər metodun qaytardığı dəyər yoxdursa — Task qaytarın, void yox; əks halda səhvi emal etmək mümkün olmur.
await-ı görməzlikdən gəlmək. Metodu çağırdınız, nəticəsini gözləmədiniz və tapşırıq öz-özünə "fire and forget" yaşayır. Siz artıq onun bitdiyini və ya səhv etdiyini bilməyəcəksiniz.
.Result və ya .Wait() ilə bloklaşdırıcı gözləmə. Deadlock almaq asandır, xüsusilə UI və ASP.NET-də. Yalnız await-dan istifadə edin.
7. Task-in qabaqcıl imkanları
Tapşırıqlar zənciri: ContinueWith
Tapşırıq bitəndən sonra yerinə yetirilməli olan əməliyyatları ContinueWith vasitəsilə "əlbəəl bağlamaq" olar:
Task.Run(() => 10)
.ContinueWith(t =>
{
Console.WriteLine($"Bitdi! Nəticə: {t.Result}");
});
Amma müasir C#-da adətən bunu async/await ilə edirlər — oxumaq daha asandır.
Nümunə: Paralel və ardıcıl məlumat yükləmə
Tutaq iki kitabı müxtəlif mənbələrdən yükləmək lazımdır. Hər iki Task-ı paralel işə salıb ikisini də gözləyə bilərsiniz:
public async Task LoadBooksFromMultipleSourcesAsync()
{
Task<List<Book>> t1 = LoadFromCloudAsync();
Task<List<Book>> t2 = LoadFromLocalAsync();
// Hər iki tapşırığı paralel gözləyirik
await Task.WhenAll(t1, t2);
// Nəticələri birləşdiririk
Books = t1.Result.Concat(t2.Result).ToList();
}
private async Task<List<Book>> LoadFromCloudAsync()
{
await Task.Delay(2000); // "Bulud"
return new List<Book> { new Book { Title = "Cloud Book", Author = "Cloud Author" } };
}
private async Task<List<Book>> LoadFromLocalAsync()
{
await Task.Delay(1000); // "Lokal disk"
return new List<Book> { new Book { Title = "Local Book", Author = "Local Author" } };
}
Diqqət edin: await ilə Task.WhenAll(...) hər iki sorğu eyni vaxtda başlayır və paralel icra olunur (əgər mümkün isə), və tətbiq onların ikisinin də bitməsini gözləyir.
8. Faydalı nüanslar
Task və Fire-and-forget
Bəzən tapşırığı işə salıb onun bitməsini gözləmək istəmirik (məsələn, logları buluda göndərmək və ya istifadəçi işləyərkən "toast" yaratmaq):
async void LogToCloudAsync(string message)
{
await Task.Run(() =>
{
// Uzun log göndərmə
Thread.Sleep(1000);
Console.WriteLine($"Log göndərildi: {message}");
});
}
Amma unutmayın: belə tapşırıqda səhv baş verərsə — bunu tapmaq çətin olacaq. Ona görə imkan varsa, Task qaytarın və ən azından istisnaları içəridə log edin!
Task və Task<TResult> real həyatda
- Klient UWP/WPF/WinForms tətbiqlərində UI-ni bloklamayın — fayllar və şəbəkə üçün uzun əməliyyatlarda Task istifadə edin.
- WebAPI/ASP.NET-də Task şəbəkə/DB gözləmə zamanı thread-ləri sərf etməməyə kömək edir, performansı artırır.
- Paralel icranı təşkil edin: eyni anda yükləyin, emal edin və yadda saxlayın.
- Demək olar ki, bütün uzun metodların Async-variantı var: File.ReadAllTextAsync, HttpClient.GetStringAsync və s.
FAQ və gözlənilməz məqamlar
Sual: Niyə bəzən Task sinxron icra olunur?
Cavab: Əgər əməliyyat artıq tamamlanıbsa (məsələn, nəticə keşlənib), compiler və/və ya scheduler metodu eyni axında sinxron olaraq tamamlaya bilər. Bu normaldır və təkrar çağırışları sürətləndirir.
Sual: Niyə async void istifadə etmək olmaz?
Cavab: Belə metodu gözləmək olmaz, onun səhvlərini tutmaq və bitməsini izləmək mümkün deyil. Task istifadə edin; async void yalnız EventHandler üçün uyğundur (məsələn, Button_Click).
Sual: Bir neçə tapşırığı işə salıb yalnız birini gözləmək olarmı?
Cavab: Bəli — Task.WhenAny istifadə edin.
GO TO FULL VERSION