CodeGym /Kurslar /C# SELF /Bir neçə istisna: Aggregat...

Bir neçə istisna: AggregateException

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

1. Giriş

Təsəvvür edin: eyni anda onlarla asynchronous task işə salırsınız — saytlardan parse edirsiniz, marketplace-lərdə qiymətləri yoxlayırsınız və ya böyük massivləri hesablayırsınız. Hər şey başlayır, amma bir neçə task xəta verir: hardasa server cavab vermir, hardasa məlumatda problem var. Sinxron kodda bir istisna görərdiniz, async-də isə xətalar bir neçə ola bilər. Burada səhnəyə AggregateException çıxır — bütün xətaları bir “konteyner”də toplayır.

AggregateException nədir?

AggregateException — .NET-də xüsusi bir istisna növüdür, paralel/async əməliyyatlardan gələn xətaları birləşdirir: Task.WhenAll, Parallel.For, TPL və s. İki və ya daha çox task xəta ilə bitdikdə onlar bir AggregateException obyektinə "yığılır".

AggregateException harada yaranır?

Ən çox yayılmış ssenari — bir neçə task-ı Task.WhenAll ilə gözləməkdir:

// Nümunə — paralel bir neçə task çağırırıq, onların bir qismi istisna atır
var tasks = new List<Task>
{
    Task.Run(() => throw new InvalidOperationException("Xəta #1")),
    Task.Run(() => throw new ArgumentException("Xəta #2")),
    Task.Run(() => { Console.WriteLine("Task 3 uğurla işləyib!"); })
};

try
{
    await Task.WhenAll(tasks); // Await burada AggregateException-i "açır"
}
catch (Exception ex)
{
    Console.WriteLine($"Xəta baş verdi: {ex.GetType().Name} - {ex.Message}");
}

Amma bir nüans var: await zamanı AggregateException açılır və birinci daxili istisna "olduğu kimi" atılır. Əgər await istifadə etməyib Result-a və ya Wait()-ə baxsanız, məhz AggregateException alacaqsınız.

2. AggregateException necə görünür?

Bu istisna InnerExceptions kolleksiyası — bütün daxil olan xətalar ilə:

try
{
    Task task = Task.WhenAll(tasks);
    task.Wait(); // Burada əgər xətalar olubsa məhz AggregateException olacaq
}
catch (AggregateException aggEx)
{
    Console.WriteLine($"Ümumi xətalar: {aggEx.InnerExceptions.Count}");
    foreach (var e in aggEx.InnerExceptions)
    {
        Console.WriteLine($"Növ: {e.GetType().Name}, Mesaj: {e.Message}");
    }
}

Çıxış nümunəsi:

Ümumi xətalar: 2
Növ: InvalidOperationException, Mesaj: Xəta #1
Növ: ArgumentException, Mesaj: Xəta #2

AggregateException necə yaranır


+--------------------------+
| Bir neçə Task işə salırıq |
+-----+---------+----------+
      |         |
      v         v
  Task1    Task2 ... TaskN
   |          |
   |  (xəta verir)
   |-------------------+
   |                   v
   |              Exception2
   v
Exception1

   |
   v
+---------------------------------+
| Task.WhenAll və ya Parallel.For |
|        (bütün istisnaları       |
|         toplayır)               |
+---------------------------------+
              |
              v
     AggregateException
   (InnerExceptions: bütün xətalar)

Niyə await ilə AggregateException görünmür?

await zamanı .NET AggregateException-i "açır" və birinci daxili istisnanı (InnerExceptions[0]) atır.

// Yalnız bir task var və o InvalidOperationException atır
try
{
    await Task.Run(() => throw new InvalidOperationException("Xəta!"));
}
catch (Exception ex)
{
    // Burada ex məhz InvalidOperationException olacaq, AggregateException deyil
    Console.WriteLine(ex.GetType().Name); // InvalidOperationException
}

Bütün xətalara daxil olmaq istəyirsinizsə — Wait() və ya Result-dən istifadə edin (amma thread-in block olmasına diqqət edin).

3. Bütün istisnaları tutmaq və emal etmək

Teoriyanı praktikaya bağlayaq. Tutaq ki, bir neçə task istifadəçi məlumatlarını işləyir və bütün uğursuzluqları loglamaq lazımdır.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MyApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var tasks = new List<Task>
            {
                Task.Run(() => throw new InvalidOperationException("Yanlış əməliyyat!")),
                Task.Run(() => throw new DivideByZeroException("Sıfıra bölmə!")),
                Task.Run(() => Console.WriteLine("Üçüncü task uğurla keçdi"))
            };

            try
            {
                // Task-ları async gözləyirik — birinci daxili istisnəni alırıq
                Task allTasks = Task.WhenAll(tasks);
                await allTasks; // await zamanı AggregateException açılır
            }
            catch (Exception ex)
            {
                // Await ilə yalnız ilk daxili istisnəni görəcəyik
                Console.WriteLine($"Birinci xəta: {ex.GetType().Name} - {ex.Message}");

                // Əgər bu AggregateException-disə — hamısını gəzə bilərik
                if (ex is AggregateException aggEx)
                {
                    foreach (var inner in aggEx.InnerExceptions)
                    {
                        Console.WriteLine($"Siyahıdakı xəta: {inner.GetType().Name} - {inner.Message}");
                    }
                }
            }

            // İndi bütün xətaları sinxron olaraq tuturuq
            try
            {
                var allTasks = Task.WhenAll(tasks);
                allTasks.Wait(); // thread-i block edir — UI-də belə etməyin!
            }
            catch (AggregateException aggEx)
            {
                foreach (var e in aggEx.InnerExceptions)
                {
                    Console.WriteLine($"[Wait] Xəta: {e.GetType().Name} — {e.Message}");
                }
            }
        }
    }
}

await istifadə edildikdə try/catch blokunda yalnız birinci xəta görünəcək. Wait() zamanı isə bütün daxili xətaları ehtiva edən klassik AggregateException gəlir.

4. Kütləvi xətaların emalı

Real tətbiqlərdə tez-tez müxtəlif xətaları fərqli şəkildə emal etmək lazımdır. Məsələn, bütün istifadəçilərə e-mail göndərirsiniz və göndərişlərin bir qismi uğursuz olur:

var sendTasks = emailList.Select(email => Task.Run(() => SendEmail(email))).ToList();

try
{
    Task.WaitAll(sendTasks.ToArray());
}
catch (AggregateException aggEx)
{
    foreach (var ex in aggEx.InnerExceptions)
    {
        if (ex is SmtpException)
            Console.WriteLine("Email göndərilməsi xətası: " + ex.Message);
        else
            Console.WriteLine("Naməlum xəta: " + ex.Message);
    }
}

Beləliklə hər uğursuzluğu qeyd edirsiniz və fərdi tədbirlər görə bilərsiniz.

5. Faydalı nüanslar

Bu davranışın səbəbləri

.NET rahatlıq məqsədilə belə edir: UI-də adətən birinci xəta kifayətdir (await zamanı). Amma serverdə və ya bütün uğursuzluqları emal etməyin vacib olduğu sistemlərdə AggregateException əvəzedilməzdir.

Pattern: Flatten — xətaların siyahısını açmaq

Metod Flatten() nested AggregateException-ləri düz siyahıya çevirir:

catch (AggregateException aggEx)
{
    foreach (var ex in aggEx.Flatten().InnerExceptions)
    {
        Console.WriteLine($"Açılmış xəta: {ex.GetType().Name} — {ex.Message}");
    }
}

Fərqli gözləmə üsullarında Task-ın davranışı

Task-ı necə gözləyirik Hansı istisnanı tutacağıq
await Task.WhenAll()
Birinci daxili xəta
Task.WhenAll().Wait()
Həmişə AggregateException
Task.WhenAll().Result
Həmişə AggregateException
Fərdi await Task tərəfindən atılan istisna, və ya əgər task AggregateException-lə bitibsə birinci daxili xəta

Yadda saxlayın

  • AggregateException eyni vaxtda çoxlu xətalar ola bilən ssenarilərdə faydalıdır.
  • await üçün birinci xətanı tutmaq rahatdır; sinxron gözləmədə isə bütün InnerExceptions-ı işləmək lazımdır.
  • Yığcam emal üçün Flatten()Handle()-dən istifadə edin.
  • UI-də sinxron və asynchronous gözləməni qarışdırmayın: freezes və deadlock əldə edə bilərsiniz.

7. AggregateException-in emalı zamanı tipik səhvlər

Mif: await Task.WhenAll həmişə AggregateException atır. Əslində belə deyil — istisna açılır və birinci daxili istisnəni görəcəksiniz.

Result-a müraciət və ya Wait()-in çağırılması task-lar bitənə qədər thread-i block edir. UI-də bu interfeysin donmasına səbəb olacaq.

Əgər bəzi xətaları "sakitcə" emal etmək istəyirsinizsə, Handle()-dən istifadə edin:

catch (AggregateException aggEx)
{
    aggEx.Handle(ex =>
    {
        if (ex is InvalidOperationException)
        {
            // Bu xətanı emal etdik, daha yuxarıya atmayaq
            return true;
        }
        return false; // Qalan istisnalar yenidən atılacaq
    });
}
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION