Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C#

วิธีจับ Exception จาก Async Void Method ใน C#

Async void methods เป็นกรณีพิเศษใน C# ที่จัดการ Exception ไม่ได้เหมือน async Task หรือ async Task<T>

ปัญหาของ Async Void

  • Exception ที่เกิดขึ้นใน async void **ไม่สามารถ Catch ด้วย try/catch รอบการเรียกได้**
  • Exception จะถูกโยนขึ้น SynchronizationContext (UI Thread) หรือ ThreadPool
  • ใน Console App: Crash Process (Unobserved Task Exception)
  • ใน ASP.NET Core: ถูก Log โดย Framework แต่ Request อาจไม่ Fail
  • ใน WPF/WinForms: Crash UI Thread หากไม่ Handle DispatcherUnhandledException

ตัวอย่างปัญหา

// ❌ ไม่สามารถ Catch ได้!
static async Task Main()
{
    try
    {
        await DoSomethingAsync(); // async void
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Caught: {ex.Message}"); // ไม่ทำงาน!
    }
}

static async void DoSomethingAsync()
{
    await Task.Delay(100);
    throw new InvalidOperationException("Error in async void!");
}
// Result: Process Crashes / Unhandled Exception

แนวทางแก้ไข

1. เปลี่ยนเป็น async Task (แนะนำสูงสุด)

// ✅ แก้ไข: Return Task แทน void
static async Task DoSomethingAsync()
{
    await Task.Delay(100);
    throw new InvalidOperationException("Error in async Task!");
}

// ตอนนี้ Catch ได้ปกติ
static async Task Main()
{
    try
    {
        await DoSomethingAsync();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Caught: {ex.Message}"); // ทำงาน!
    }
}

2. ใช้ Task.Run + Wait (สำหรับ Legacy Code ที่เปลี่ยน Signature ไม่ได้)

static void Main()
{
    try
    {
        // รันใน Task และรอ ผ่าน GetAwaiter().GetResult()
        Task.Run(async () => await DoSomethingAsync()).GetAwaiter().GetResult();
    }
    catch (Exception ex)
    {
        // จะได้ AggregateException ต้อง Unwrap
        Console.WriteLine($"Caught: {ex.InnerException?.Message ?? ex.Message}");
    }
}

3. Global Exception Handler (สำหรับ UI Apps)


// WPF
Application.Current.DispatcherUnhandledException += (s, e) =>
{
    LogError(e.Exception);
    e.Handled = true; // ป้องกัน Crash
};

// WinForms
Application.ThreadException += (s, e) => LogError(e.Exception);
TaskScheduler.UnobservedTaskException += (s, e) =>
{
    LogError(e.Exception);
    e.SetObserved(); // ป้องกัน Crash Process
};

4. Fire-and-Forest กับ Error Handling ภายใน

// หากต้องใช้ async void จริงๆ (Event Handler) ให้ Handle ภายใน
private async void Button_Click(object sender, EventArgs e)
{
    try
    {
        await DoWorkAsync();
    }
    catch (Exception ex)
    {
        // Handle/UI Feedback ที่นี่
        MessageBox.Show($"Error: {ex.Message}");
    }
}

สรุป Best Practices

  1. หลีกเลี่ยง async void ยกเว้น Event Handlers
  2. ใช้ async Task สำหรับทุกอย่างที่อาจโยน Exception
  3. Event Handler: Try/Catch ภายใน Method เอง
  4. ตั้งค่า Global Handler สำหรับ Unobserved Exceptions
  5. ใช้ Task.Run(...).GetAwaiter().GetResult() เพื่อ Bridge Sync → Async Void