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