async void 仅允许用于 UI 事件处理器(如 WinForms/WPF 按钮点击),因其委托签名强制返回 void;禁止用于自定义事件、命令、ViewModel 方法及 ASP.NET Core Action,否则引发崩溃或异常丢失。
这是唯一被官方默许的 async void 使用场景。因为 .NET 的事件委托签名强制要求返回 void(如 EventHandler、RoutedEventHandler),你无法把按钮点击事件改成返回 Task——那会直接编译失败。
所以当你写 private async void btnDownload_Click(object sender, EventArgs e) 时,不是“偷懒”,而是被框架签名锁死的无奈选择。
Button.Click、WPF Button.Click、UWP Button.Click 等 UI 事件ICommand.Execute)、ViewModel 中的任何方法、测试方法中的调用async void ——它不走事件循环,用就挂(500 错误且无堆栈)async void 方法里抛出的异常不会装进 Task,也不会被上层 try-catch 捕获。它会顺着当前活跃的同步上下文(比如 WinForms 的 UI 线程消息泵)直接往上抛,最终触发 AppDomain.UnhandledException 或 Application.ThreadException ——轻则弹窗崩溃,重则静默退出。
这意味着你写的 try-catch 只能兜住 await 之后的代码,但若 await 前就出错(比如参数校验失败),照样飞出去。
async void 方法内部做完整异常防护:整个方法体包在 try/catch/finally 里Application.ThreadException 作为兜底;WPF 中监听 Application.DispatcherUnhandledException
try 到这个方法的异常你不能在另一个异步方法里写 await btnDownload_Click(...) ——编译器直接报错:“无法等待 void”。这导致两个实际问题:
await 控制顺序)async Task 方法,在 async void 里仅做调度和错误兜底private async void btnDownload_Click(object sender, EventArgs e)
{
btnDownload.Enabled = false;
try
{
await DownloadAndShowAsync(); // ✅ 核心逻辑抽离为 Task
}
catch (Exception ex)
{
MessageBox.Show($"下载失败:{ex.Message}");
}
finally
{

btnDownload.Enabled = true;
}
}
private async Task DownloadAndShowAsync()
{
var data = await httpClient.GetStringAsync(url);
textBox.Text = data;
}
如果你在 async void 方法里不小心用了 .Result、.Wait() 或没加 ConfigureAwait(false),而调用栈又涉及 UI 同步上下文(比如从 WPF Dispatcher.Invoke 内部发起),就可能卡死主线程 —— 因为 await 试图回调回 UI 线程,但 UI 线程正等着你那个 .Wait() 返回。
await 后的操作,默认都应加 .ConfigureAwait(false)(除非你明确需要回到 UI 上更新控件).Wait()、.Result、.GetAwaiter().GetResult()
async void 一起用,就是定时炸弹async void LoadData(),还觉得“反正能跑”。这种隐患不会立刻报错,但会在某次发布后某个角落突然崩掉,而且查无痕迹。