CodeGym /Kurslar /C# SELF /Siniflər Task və <...

Siniflər Task və Task<TResult>

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

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
Status
Tapşırığın cari vəziyyəti
Result
Task<TResult> üçün nəticə (axını bloklayır)
IsCompleted
Tapşırıq başa çatıb mı
IsFaulted
Tapşırıqda istisna baş veribmi
IsCanceled
Tapşırıq ləğv edilibmi
Wait()
Cari axını bitənə qədər bloklayır (təhlükəli)
ContinueWith()
Bitişdən sonra başqa bir tapşırığı işə sal
Exception
Əgər Task səhv ilə bitdisə, istisnaya giriş
Id
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
async Task / async Task<T>
Asinxron əməliyyat Adətən I/O, rahatlıq
Task.Run
Task.Run(() => { ... })
Fon tapşırıq CPU-bound (həcmli hesablamalar)
TaskCompletionSource<T>
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.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Task istifadə edərək nəticəni qaytarmaq
Task istifadə edərək nəticəni qaytarmaq
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION