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 |
|---|---|
|
Birinci daxili xəta |
|
Həmişə AggregateException |
|
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() və 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
});
}
GO TO FULL VERSION