CodeGym /Kurslar /C# SELF /Fire and forget tapşırıqlarında istisnalar

Fire and forget tapşırıqlarında istisnalar

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

1. Fire and forget nədir?

Proqramlaşdırmada fire and forget termini tapşırığın onun bitməsini gözləmədən işə salınması deməkdir. C# və .NET dünyasında bunu adətən Task ilə edirlər: tapşırığı işə salırlar, amma heç yerdə gözləmirlər (await etmirlər), referans saxlamırlar və əslində onu unudurlar.

// Düymə fon tapşırığını işə salır, amma heç yerdə await olunmur.
button.Click += (s, e) =>
{
    Task.Run(() => DolgayaOperatsiya());
};

Cəlbedicidir: "gəl fon işinə davam etsin, mən öz işimi görəm". Amma belə yanaşmada, əgər tapşırığın içində istisna baş verərsə, heç kim vaxtında bundan xəbər tutmayacaq — o sakitcə itə bilər.

2. Task-də istisnaların işlənməsi necə işləyir

Klasiya: await və səhvlərin tutulması

Asinxron tapşırıqlarla işləməyin standart yolu — await-dan istifadədir. Əgər tapşırıqda səhv baş verərsə, o gözləmə nöqtəsində atılacaq:

try
{
    await SomeOperationAsync(); // əgər içində Exception olsa, o catch-ə düşəcək
}
catch(Exception ex)
{
    Console.WriteLine("Uups! Tapşırıqda səhv oldu: " + ex.Message);
}

Yəni tapşırığın tamamlanmasını gözlədikdə, istisnanı qaçırmayacaqsınız.

Amma fire-and-forget tapşırıqları heç kim gözləmir!

public void IshleyiGozlemedenBaslat()
{
    // Tapşırıq özü-özünə işləyir. Heç kim onu gözləmir...
    Task.Run(() => {
        // Haradasa içində problem yaranır:
        throw new InvalidOperationException("Oj, hamısı itdi!");
    });
    // Metod bitdi, tapşırıq sakitcə fonda işləyir.
}

Belə tapşırığın içində istisna baş verərsə, o əsas thread-də atılmayacaq. Proqram işləməyə davam edəcək, sanki heç nə olmamış kimi.

Vacib

.NET-də işlənməmiş istisna ilə tapşırıq Faulted vəziyyətinə keçir. Amma əgər siz onu gözləmirsinizsə (await, .Result, .Wait() və s.), heç kim o istisnanı oxumayacaq və o çağıran kodda görünməyəcək.

Qapağın altında nə baş verir?

Heç kəs gözləmədiyi tapşırıqlar üçün tək ümid — TaskScheduler.UnobservedTaskException hadisəsidir. Bu hadisə GC (garbage collector) işlədikdə və işlənməmiş istisnalı tapşırıq tapdıqda tetiklenir. Amma bu dərhal olmur və gözlədiyiniz yerdə olmaya bilər — ona güvənmək olmaz.

3. Demonstrasiya: Fire-and-forget səhvi

// Nümunə: Main-dən birbaşa fire-and-forget tapşırıq işə salırıq
using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        FireAndForgetExample();

        Console.WriteLine("Əsas thread işləməyə davam edir...");
        // Tapşırığa bitmək üçün vaxt verək
        Task.Delay(2000).Wait();
    }

    static void FireAndForgetExample()
    {
        Task.Run(() =>
        {
            Console.WriteLine("Fire-and-forget tapşırıq başladı!");
            Task.Delay(500).Wait();
            throw new InvalidOperationException("Fire-and-forget tapşırığın içində səhv!");
        });
    }
}

Bu kodu işlətsəniz... xüsusi bir şey baş verməyəcək. Səhv baş verəcək, amma proqram bundan xəbərdar olmayacaq. Bəzən IDE-nin Output pəncərəsində xəbərdarlıq görünə bilər, amma istifadəçiyə — heç nə.

Real layihələrdə niyə təhlükəlidir?

  • Çətin reproduksiya olunan səhvlər ("bəzən işləmir — niyə mənə məlum deyil").
  • Məlumatın və ya loqikanın sakitcə itməsi (məsələn, müştəriyə məktub göndərilmədi).
  • Production-da monitorinq yoxdursa problemlərin siqnalı olmaması.

4. Fire-and-forget-də səhvləri düzgün işləmək yolları

Loqlama və səhvlərin tapşırığın içində işlənməsi

Minimum təhlükəsiz səviyyə — istisnaları birbaşa fire-and-forget tapşırığının içində tutmaq:

Task.Run(() =>
{
    try
    {
        // Sizin uzun/təhlükəli kodunuz
        throw new InvalidOperationException("Bir şey səhv getdi!");
    }
    catch (Exception ex)
    {
        // Səhvi loqlayırıq və ya istifadəçiyə məlumat veririk
        Console.WriteLine("Fire-and-forget: tutulan istisna: " + ex.Message);
        // Fayla yazmaq, alert sistemi ilə inteqrasiya və s. mümkündür
    }
});

Asinxron void metodları (və niyə bu yaxşı fikir deyil)

async void TehlikeliFireAndForget()
{
    // Nəsə təhlükəli
    throw new Exception("Bum!");
}

async void metodlar faktiki olaraq fire-and-forget-dir: onları gözləmək mümkün deyil, onlar Task qaytarmır. Onlardan gələn istisnalar tətbiqin qlobal handler-ına (məsələn, AppDomain.UnhandledException) düşür və çox vaxt prosesin çöküşünə səbəb ola bilər. async void-dan yalnız event handler-larda istifadə edin — və yenə ehtiyatla.

Səhvlərin işlənməsi üçün yardımcı metodlardan istifadə

Fire-and-forget-in təhlükəsiz işə salınmasını ayrıca wrapper-a çıxarmaq rahatdır:

// Fire-and-forget-in təhlükəsiz işə salınması üçün universal metod
public static void RunSafeFireAndForget(Func<Task> taskFactory)
{
    Task.Run(async () =>
    {
        try
        {
            await taskFactory();
        }
        catch (Exception ex)
        {
            // İstisnanı loqlayırıq
            Console.WriteLine("Fire-and-forget (safe): " + ex);
            // Monitoring sisteminə göndərmək kimi əlavə addımlar mümkün!
        }
    });
}

// İstifadə:
RunSafeFireAndForget(async () =>
{
    await Task.Delay(1000);
    throw new InvalidOperationException("Fire-and-forget içində!");
});

Həqiqi həyat nümunəsi: email göndərilməsi

// Göndərmə düyməsi:
private void buttonSend_Click(object sender, EventArgs e)
{
    Task.Run(() => SendEmail());
}

// Göndərmə metodu:
private void SendEmail()
{
    try
    {
        // Burada real göndərmə ola bilər
        throw new Exception("SMTP-server əlçatmazdır!");
    }
    catch (Exception ex)
    {
        // Loqlama
        File.AppendAllText("errors.log", $"Email göndərmə səhvi: {ex.Message}\n");
    }
}

5. UnobservedTaskException ilə nə baş verir?

Ekstremal halda .NET TaskScheduler.UnobservedTaskException hadisəsini təmin edir. Bu hadisə tapşırıq səhvlə bitdikdə, heç kim onu gözləmədikdə və tapşırıq obyektini GC topladıqda çağırılır. Buna etibar etmək olmaz — bu "son şans" mexanizmidir.

TaskScheduler.UnobservedTaskException += (sender, e) =>
{
    Console.WriteLine("Qlobal UnobservedTaskException: " + e.Exception);
    e.SetObserved(); // Bunu çağırmağı unutmayın, yoxsa tətbiq avariya ilə bağlana bilər!
};

Əlavə məlumat: TaskScheduler.UnobservedTaskException.

6. Faydalı nüanslar

Yanaşmaların sxematik müqayisəsi

Yanaşma İstisnalar işlənib? Səhvləri harada tutmaq Səhvi "itirmək" riski
await
Bəli Çağıran kodda Aşağı
Fire-and-forget try/catch-siz Xeyr Heç yerdə Çox yüksək
Fire-and-forget try/catch ilə Bəli Tapşırığın özündə Aşağı (əgər loqlayırsınızsa)
async void-metod Xeyr (qlobalə düşür) Qlobal handler Yüksək

Fire-and-forget necə dizayn edilməlidir

  • Əgər tapşırığın nəticəsi və ya vəziyyəti kritikdirsə — fire-and-forget etməyin. await və ya sonra gözləmək üçün Task saxlayın.
  • Fire-and-forget yalnız həqiqətən əhəmiyyətsiz fon tapşırıqları üçün məntiqlidir (məsələn, telemetriya göndərmə).
  • Həmişə fire-and-forget-i öz metoduna bükün və istisnaları tutub loqlayın.
  • Mürəkkəb fon senariləri üçün növbələr və worker-lər istifadə edin: Hangfire, Quartz.NET.

Praktik tətbiq və müsahibələr

Müsahibələrdə tez-tez soruşurlar: "Fire-and-forget tapşırıqda istisna baş verərsə nə olar?" və ya "Niyə hər yerdə async void istifadə etmək olmaz?" Düzgün cavab: fon tapşırıqlarındakı səhvlərin taleyinə yalnız siz cavabdehsiniz — ya tutub loqlayacaqsınız və analiz edəcəksiniz, ya da ruh şəklində səhvlər əldə edəcəksiniz.

"fire-and-forget" və await müqayisəsi

Ssenari Səhvlərin işlənməsi etibarlılığı Tətbiq sahəsi
Adi await Əla Nəticə lazım olduqda və ya success/fail vacibdirsə hər yerdə
Fire-and-forget Poor (əgər əllə işlənməzsə) Yalnız həqiqətən fon və əhəmiyyətsiz tapşırıqlar üçün
Fire-and-forget try/catch ilə Yaxşı (əgər loqlayırsa) Nəticə tələb olunmasa da xətalar haqqında məlumat vacibdirsə

Növbəti mühazirədə bir neçə nəticə qaytaran paralel tapşırıqlarda səhvlərin işlənməsini müzakirə edəcəyik. İndi isə yadınızda saxlayın: əgər hardasa "atdınızsa", hədəfə çatdığını yoxlayın!

7. Fire-and-forget tapşırıqlarla işləyərkən tipik səhvlər

Səhv №1: Fire-and-forget-də istisnaların görməməzlikdən keçirilməsi.
Yeni başlayanlar ümid edir ki, istisnalar "hər yerdə üzə çıxacaq". try-catch və loqlama olmadan onlar itir və aşkar edilməyən bug-lara səbəb olur.

Səhv №2: Event handler-lar xaricində async void-dan istifadə etmək.
Belə metodlar istisnaları qlobal handler-a atır (məsələn, AppDomain.UnhandledException), bu isə tətbiqin qəfil bağlanmasına gətirə bilər.

Səhv №3: Həddindən artıq istisna tutulması.
Tapşırığın içində bütün istisnaları tutmaq bəzi problemləri gizlədə bilər, onları çağıran kodda işləmək daha yaxşı olardı və bu debugging-i çətinləşdirə bilər.

Səhv №4: Loqlamanın laqeydliyi.
Fire-and-forget tapşırıqlarda loqlama olmadan production-da səhvlər barədə bilməyəcəksiniz.

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