CodeGym /Kurslar /C# SELF /Asinxron kodda istisnaların işlənməsi

Asinxron kodda istisnaların işlənməsi

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

1. Asinxron metodlar və istisnalar

Biz adətən kodda nəsə pis baş verərsə (məsələn, sıfıra bölmə və ya mövcud olmayan fayla müraciət) istisna atıldığını və bunu try-catch ilə tutmağın mümkün olduğunu bilirik. Hər şey sadədir, əgər kod ardıcıl və bir thread-də icra olunur. Amma asinxronluq ortaya çıxanda dünya kosmik məkan kimi olur: istisna gözlədiyimiz yerdən kənarda "görünə" bilər və ya ümumiyyətlə nəzərdən qaça bilər.

Səbəb odur ki, asinxron metod adətən bir tapşırıq (Task) qaytarır və onun icrası metoddan çıxandan SONRA davam edə bilər. İstisna əsas thread tapşırığı "buraxdıqdan" sonra baş verə bilər. Buna görə asinxron metodun çağırışı ətrafında adi try-catch sinxron koddakı kimi işləməyə bilər.

Gəlin sadə nümunə ilə izah edək. Tutaq ki, bizim mini-tətbiqdə belə bir asinxron metod var:

// Bizim tətbiqin fraqmenti: asinxron "hesabat göndərmə" hesablaması
public async Task SendReportAsync()
{
    // Burada şəbəkə çağırışları və ya fayl əməliyyatları ola bilər
    await Task.Delay(100);
    throw new InvalidOperationException("Hesabatın göndərilməsində səhv!");
}

Və bunu belə çağıra bilərik:

SendReportAsync();
Console.WriteLine("İş davam edir...");

Vizuallaşdırma

flowchart TD
    Start["Main axını"]
    Call[/"SendReportAsync() çağırışı"/]
    Continue["İş davam edir..."]
    Exception["Task daxilində istisna yaranır"]
    Unhandled["İstisna emal olunmadı!"]
    Start --> Call --> Continue
    Call -.- Exception --> Unhandled

Nəticə: əgər asinxron metod Task qaytarır və siz tapşırığın yekununu gözlə( məs. await və ya .Wait()), istisna "görünməz" olacaq. Ən yaxşı halda runtime loga "Unobserved task exception" kimi yazacaq. Pis halda isə siz səhvi itirə və uzun müddət "mistik bug" mənbəyini axtara bilərsiniz.

2. Asinxron kodda istisnaları necə düzgün tutmaq?

await + try-catch istifadə edin

Doğru yanaşmaya baxaq:

try
{
    await SendReportAsync(); // Task-ın bitməsini gözləyirik
    Console.WriteLine("Hesabat uğurla göndərildi!");
}
catch (Exception ex)
{
    Console.WriteLine($"Upps! Nəsə yanlış oldu: {ex.Message}");
}

Necə işləyir? Əgər siz asinxron metoda await qoyursunuzsa, C# sizin metodu await-dən əvvəl və sonra olmaqla iki hissəyə bölür. Əgər asinxron hissədə istisna baş verərsə, o məhz await-in yerləşdiyi yerdə "çıxacaq" və klassik try-catch ilə tutulacaq.

Tətbiq üçün nümunə

Demo-ya hesabat göndərmə xətalarının işlənməsini əlavə edək:

public async Task StartReportProcessAsync()
{
    try
    {
        await SendReportAsync();
        Console.WriteLine("Hesabat uğurla göndərildi!");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Hesabatın göndərilməsində xəta: {ex.Message}");
    }
}

Və çağıraq:

await StartReportProcessAsync();

.Wait(), .Result — konsol üçün pis deyil, amma optimal deyil

Bəzən, xüsusən konsol tətbiqlərində, üst səviyyədə await istifadə etmək olmaz (köhnə C# versiyaları, Main metodu). Bu zaman sinxron gözləmək üçün .Wait() və ya .Result-dən istifadə olunur.

try
{
    SendReportAsync().Wait();
}
catch (AggregateException aggEx)
{
    foreach (var ex in aggEx.InnerExceptions)
        Console.WriteLine($"Səhv: {ex.Message}");
}

Niyə belə? .Wait().Result çağırışları orijinal istisnanı həmişə AggregateException-ə bükür. Bu bir konteynerdir və içində bir və ya bir neçə istisna ola bilər. Ona görə də daxili istisnaları döngü ilə açmaq lazımdır. AggregateException haqqında rəsmi sənədlərdə daha çox məlumat var: rəsmi sənədlər.

Vacib!

Müasir .NET versiyalarında (C# 7.1-dən etibarən) siz asinxron Main təyin edə və giriş nöqtəsində birbaşa await istifadə edə bilərsiniz:

static async Task Main(string[] args)
{
    await StartReportProcessAsync();
}

3. "Fire-and-forget" tapşırıqlarda istisnalar

Əgər siz asinxron metodu çağırdınız, onun bitməsini gözləmədiniz və tapşırığa referans saxlamadınız, nə olar?

SendReportAsync(); // Tapşırığı "unutduq"

Belə vəziyyətdə problem yaranır: tapşırıq daxilində baş verən istisna heç vaxt emal olunmaya bilər. Bəzən (mühitə və tənzimləmələrə görə) tətbiq qəza edə bilər, bəzən isə sadəcə xəbərdarlıq loglanar. Bu C#-ın bug-u deyil, Task mexanizminin nəticəsidir.

Necə düzgün etmək lazımdır?

  • Ən yaxşısı: əgər tapşırıq qəza edə biləcəksə, "fire-and-forget" istifadə etməyin.
  • Əgər metod mütləq "fire-and-forget" olmalıdırsa, metodun daxilində xətaları açıq şəkildə işləyin.
public async Task SendReportSafeAsync()
{
    try
    {
        await Task.Delay(100);
        throw new InvalidOperationException("Göndərilmədə səhv!");
    }
    catch (Exception ex)
    {
        // Loglayırıq və ya emal edirik
        Console.WriteLine($"[Log] İstisna: {ex.Message}");
    }
}

// Çağırış
SendReportSafeAsync();

Ümumi tövsiyə: Əgər tapşırıq heç kim tərəfindən izlənilmir və siz await istifadə etmirsinizsə, asinxron metodun bədənini mütləq try-catch ilə əhatə edin. Beləliklə, siz xətanı itirməyəcək və ən azından onu loglayacaqsınız.

4. İstisnalar və paralel tapşırıqlar: Task.WhenAll və s.

Reallıqda tez-tez bir neçə müstəqil asinxron tapşırığı eyni vaxtda başlatmaq və onların bitməsini gözləmək lazımdır. Məsələn, hesabatları birdən çox ünvanlara paralel göndərirsiniz:

var tasks = new List<Task>
{
    SendReportAsync(),
    SendReportAsync(),
    SendReportAsync()
};

await Task.WhenAll(tasks);

Əgər bir və ya bir neçə tapşırıq istisna atsa, nə olacaq?

Belə xətaları necə tutmaq olar?

await ilə Task.WhenAll(tasks) istifadə etdikdə — əgər ən azı bir tapşırıq səhvlə bitibsə, await səhvə ilk tamam olan səhvli tapşırığın istisnasını atacaq (o adətən AggregateException-ə bükülmür).
Amma bir nüans var: əgər tapşırıqlar arasında bir neçə uğursuzluq varsa, onda AggregateException daxili istisnaların dəsti ilə atıla bilər.

try
{
    await Task.WhenAll(tasks);
}
catch (Exception ex)
{
    // Əgər bu AggregateException-disə, onu açıq
    if (ex is AggregateException agg)
    {
        foreach (var inner in agg.InnerExceptions)
            Console.WriteLine($"Tapşırıqda səhv: {inner.Message}");
    }
    else
    {
        Console.WriteLine($"Səhv: {ex.Message}");
    }
}

Bir tək Task üçün await istifadə edərkən istisna adətən AggregateException-ə bükülmür. Amma WhenAll ilə bu mümkün ola bilər!

5. Asinxron delegatlar və xətaların işlənməsi

UI tətbiqlərində (WPF, WinForms, ASP.NET) hadisə işləyiciləri çox vaxt asinxron lambdalar şəklində yazılır. Əgər belə bir işləyicidə istisna "xaricə" çıxarsa, nəticə UI çərçivəsindən asılıdır: tətbiq ya qəza edər, ya da xətanı udar.

Tövsiyə

Asinxron delegatlarda həmişə try-catch istifadə edin:

button.Click += async (sender, args) =>
{
    try
    {
        await SendReportAsync();
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Səhv: {ex.Message}");
    }
};
1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
Asinxron proqramlaşdırma
Asinxronluq vs. Çoxaxınlılıq
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION