CodeGym /Kurslar /C# SELF /Bir neçə tapşırığın idarə edilməsi

Bir neçə tapşırığın idarə edilməsi

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

1. Task.WhenAll: hamısını eyni anda gözləmək

Həqiqi həyatda nadir hallarda təkcə bir şeyi edirsiniz. Səhər yatdığınız yerdən qalxanda saatı yoxlayırsınız, duş qəbul edirsiniz və mahnı oxuyursunuz, geyinib musiqi dinləyirsiniz, eyni zamanda özünüz üçün qəhvə hazırlayırsınız. Yaxud kompüterdə işləyirsinizsə, eyni vaxtda bir neçə fayl yükləyirsiniz, bir sıra şəbəkə sorğuları göndərirsiniz, müxtəlif istiqamətlər üzrə çoxlu hesablamalar aparırsınız. Bütün bu əməliyyatlar paralel (və ya asinxron) icra oluna bilər (və olmalıdır), beləliklə istifadəçinin vaxtını boşuna sərf etmirsiniz. Biz istəmirik ki, hər bir tapşırığın bitməsini bir-bir gözləyək! Belə "orkestri" necə təşkil etmək olar? Necə anlamaq olar ki, HAMISI bitdi? Yaxud əksinə, kim əvvəl bitdi onu necə öyrənmək olar?

Task.WhenAllTask.WhenAny — məhz bu ssenarilər üçün lazım olan alətlərdir.

WhenAll metodu

Task.WhenAllTask sinfinin statik metodu olub, tapşırıqların kolleksiyasını qəbul edir və yalnız ötürülən bütün tapşırıqlar tamamlandıqda tamamlanan yeni bir tapşırıq qaytarır. Bu, imtahanda bütün kurs yoldaşlarınız biletini təhvil verənə qədər auditoriyadan çıxmamağınız kimi bir şeydir.

Metodun imzası

Task Task.WhenAll(params Task[] tasks)
Task<TResult[]> Task.WhenAll<TResult>(params Task<TResult>[] tasks)

Versiyalar nəticə qaytaran tapşırıqlar (Task<TResult>) və "boş" (Task) üçün olur.

Ən sadə nümunə: bütün faylların yüklənməsini gözləmək

Gəlin üç faylı asinxron şəkildə yükləməyi sınayaq. Sadələşdirmək üçün yüklənməni gecikmə ilə təqlid edəcəyik (Task.Delay). Nümunələr realdır, kod işləkdir.

using System;
using System.Threading.Tasks;

class Program
{
    // Faylın yüklənməsini gecikmə ilə simulyasiya edirik, adını qaytarırıq
    static async Task<string> DownloadFileAsync(string fileName)
    {
        Console.WriteLine($"► Yükləməyə başlayırıq: {fileName}");
        await Task.Delay(2000); // 2 saniyə gözləyirik
        Console.WriteLine($"✓ Yükləmə tamamlandı: {fileName}");
        return fileName;
    }

    static async Task Main()
    {
        var files = new[] { "fileA.txt", "fileB.txt", "fileC.txt" };

        // Bütün yükləmələri eyni anda başladırıq
        var downloadTasks = new Task<string>[files.Length];
        for (int i = 0; i < files.Length; i++)
        {
            downloadTasks[i] = DownloadFileAsync(files[i]);
        }

        // Bütün yükləmələrin tamamlanmasını gözləyirik
        string[] results = await Task.WhenAll(downloadTasks);

        Console.WriteLine($"Bütün yükləmələr tamamlandı! Siyahı: {string.Join(", ", results)}");
    }
}

Burada nə baş verir?

  • Biz hər bir tapşırığı növbə ilə gözləmirik. Hər üçü paralel (asinxron) başlanır.
  • Task.WhenAll(downloadTasks) onu qaytaran tapşırığı yalnız bütün tapşırıqlar bitəndə tamamlanmış hesab edir.
  • Sonra bütün tapşırıqların nəticələri ilə işləyə bilərik — onları istifadə etmək, ekrana çıxarmaq, daha sonra göndərmək olar.

Analogiya

Bu, üç kuryerə üç paket təhvil verməyi tapşırıb, hamısının müştərilərə çatdığını biləndən sonra rəhbəri xəbərdar etmək kimidir.

Task.WhenAll iş mexanizminin sxemi

sequenceDiagram
    participant Program
    participant Tapshiriq1
    participant Tapshiriq2
    participant Tapshiriq3

    Program->>Tapshiriq1: Başladılır
    Program->>Tapshiriq2: Başladılır
    Program->>Tapshiriq3: Başladılır
    Tapshiriq1-->>Program: Tamamlandı (digərindən əvvəl ola bilər)
    Tapshiriq2-->>Program: Tamamlandı
    Tapshiriq3-->>Program: Tamamlandı
    Program->>Program: Task.WhenAll tamamlandı, bütün nəticələr mövcuddur

Əgər tapşırıqlardan biri səhvlə bitərsə nə olur?

Task.WhenAll heç bir tapşırıq səhvlə bitəndə dayanmır. O, bütün tapşırıqların tamamlanmasını gözləyir. Əgər ən azı birində istisna baş veribsə — nəticə tapşırığı Faulted vəziyyətinə gələcək və içində baş vermiş bütün istisnələrin kolleksiyası olacaq.

Nümunə

static async Task<string> MayThrowAsync(string fileName)
{
    await Task.Delay(500);
    if (fileName == "fileB.txt")
        throw new Exception("fileB.txt yüklənməsi xətası");
    return fileName;
}

static async Task Main()
{
    var files = new[] { "fileA.txt", "fileB.txt", "fileC.txt" };
    var downloadTasks = files.Select(MayThrowAsync).ToArray();
    try
    {
        string[] results = await Task.WhenAll(downloadTasks);
        Console.WriteLine($"Hər şey yaxşıdır: {string.Join(", ", results)}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Ən azı bir tapşırıq səhvlə bitdi: {ex.Message}");
    }
}

İstisna halında bütün daxili xətalara baxmaq üçün AggregateException.InnerExceptions istifadə edin.
Rəsmi sənədləşmə: Task.WhenAll docs

2. Task.WhenAny: ilk tamamlanan tapşırığı gözləmək

Task.WhenAny də statik metoddur, lakin o, ötürülən tapşırıqlardan hər hansı biri "Tamamlandı" vəziyyətinə keçən kimi öz işi tamamlayır (uğurla ya da səhvlə olmasına baxmayaraq). O, ilk tamamlanan tapşırığa istinad qaytarır.

İmza

Task<Task> Task.WhenAny(params Task[] tasks)
Task<Task<TResult>> Task.WhenAny<TResult>(params Task<TResult>[] tasks)

Analogiya

Kimin ilk şirniyyatı bişirdiyini düşünün — o, "qalib" olur; digərinə isə dayanmaq tapşırıla bilər. Bəzən kifayətdir ki, bir neçə serverdən hansı daha tez cavab verdi onu istifadə edəsiniz.

Nümunə: kim daha sürətlidir?

using System;
using System.Threading.Tasks;

class Program
{
    // Müxtəlif sürətlə işləyən sorğunu simulyasiya edirik
    static async Task<string> RequestAsync(string name, int delay)
    {
        await Task.Delay(delay);
        return $"{name} {delay} ms-də tamamlandı";
    }

    static async Task Main()
    {
        var taskA = RequestAsync("A", 1000);
        var taskB = RequestAsync("B", 700);    // ən sürətlisi
        var taskC = RequestAsync("C", 1500);

        // İlk tamamlanan tapşırığı gözləyirik
        Task<string> finished = await Task.WhenAny(taskA, taskB, taskC);

        Console.WriteLine($"İlk tamamlandı: {finished.Result}");
    }
}

WhenAny ilə hansı tapşırığın əvvəl çatdığı məlum olur. Bundan sonra digər tapşırıqları, məsələn, CancellationToken ilə ləğv etmək olar (bu növbəti mühazirələrin mövzusudur).

Niyə WhenAny tapşırığı özü geri qaytarır, nəticəni yox?

Çünki metod hansı tapşırığın əvvəl tamamlanacağını bilmir: hansı növ nəticə qaytaracağını öncədən təyin etmək mümkün deyil. Ona görə də o, həmin tapşırığın özünü qaytarır — nəticəni çıxarmaq üçün sonra await və ya tapşırığın Result xassəsindən istifadə etməlisiniz.

WhenAll vs WhenAny: müqayisəli təsvir

Metod Nə vaxt işə düşür Nə qaytarır Tipik ssenari
Task.WhenAll
Hamısı tamamlandıqda Tapşırıq (Task/Task<T[]>) Bütün cavabları və ya faylları gözləyib nəticələri toplamaq
Task.WhenAny
Bunlardan hər hansı biri tamamlandıqda İlk tamamlanan tapşırığın özü İlkin nəticəni istifadə etmək, digər tapşırıqları ləğv etmək

3. WhenAllWhenAny kombinasiyası — real həyat ssenariləri

Bəzən əvvəlcə hər hansı bir tapşırığın tamamlanmasını, sonra isə hamısını gözləmək lazım gəlir. Və ya əksinə.

Nümunə: iki server arasında "ping-pong"

Təsəvvür edin ki, həm sürətli, həm də yavaş klon serverə sorğu göndərirsiniz — əgər biri yavaşdırsa, digərindən nəticə götürmək üçün. Nəticəni ilk əldə edilən istifadə edirsiniz.

static async Task Main()
{
    var fastServer = RequestAsync("Tez server", 400);   // 400 ms
    var slowServer = RequestAsync("Yavaş server", 2000); // 2000 ms

    var completed = await Task.WhenAny(fastServer, slowServer);
    Console.WriteLine(await completed);

    // *Istəyə bağlı*: burada hələ tamamlanmamış tapşırıqları CancellationToken ilə ləğv etmək olar
}

Nümunə: bütün məlumat yüklənəndən sonra prosesi başlatmaq

Proqramınızda üç məlumat mənbəyi var. Yalnız hər üçü yüklənəndən sonra emala keçmək lazımdır:

static async Task Main()
{
    var task1 = DownloadFileAsync("a.txt");
    var task2 = DownloadFileAsync("b.txt");
    var task3 = DownloadFileAsync("c.txt");

    var all = await Task.WhenAll(task1, task2, task3);

    ProcessFiles(all[0], all[1], all[2]);
}

4. Task.WhenAllTask.WhenAny ilə işləyərkən tipik səhvlər

Səhv №1: WhenAll-da ilk tapşırığın tamamlanmasını gözləmək.
Yeni başlayanlar düşünürlər ki, Task.WhenAll ilk tapşırıq tamamlandıqda bitəcək. Əslində o, bütün tapşırıqların tamamlanmasını gözləyir.

Səhv №2: WhenAll-da istisnaları nəzərə almamaq.
Əgər tapşırıqlardan biri istisna atsa, nəticə tapşırığı Faulted vəziyyətinə gələcək. AggregateException.InnerExceptions-i emal etmədən vacib xətaları qaçırmaq olar.

Səhv №3: WhenAny-də Result-a yoxlamadan giriş.
Əgər ilk tamamlanan tapşırıq səhvlə bitibsə, Result-a müraciət istisna atacaq. Əvvəl Task.IsFaulted-ı yoxlayın.

Səhv №4: tapşırıqları paralel əvəzinə ardıcıl başlatmaq.
Məsələn, dövrdə hər tapşırığı await ilə gözləmək və ya Task.WhenAll istifadə etməmək performansı kəskin şəkildə azaldır.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION