Winforms应用程序在未处理的异常处理程序后仍然崩溃

Jay*_*cee 6 c# winforms async-await

我在测试应用程序中应用了这些处理程序:

    Application.ThreadException += Application_ThreadException;
    Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
    AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
Run Code Online (Sandbox Code Playgroud)

然后我在窗体上的嵌套Task/Async await上创建一个异常:

尽管处理程序被解雇 - CurrentDomain.UnhandledException应用程序仍然崩溃.

为什么会崩溃而不显示对话框并保持运行?

System.Exception未处理消息:mscorlib.dll中出现未处理的"System.Exception"类型的异常附加信息:dd

private async void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine("Main " + Thread.CurrentThread.ManagedThreadId);
    try
    {
        await Hello();
    }
    catch (Exception ex)
    {
        Console.WriteLine("Exception on main  " + Thread.CurrentThread.ManagedThreadId);
    }
}

private async static Task Hello() //changed from void
{
    await Task.Run(() => new IGetRun().Run1());
}

internal class IGetRun
{
    public async Task Run1() //changed from void
    {
        Console.WriteLine("Run1 " + Thread.CurrentThread.ManagedThreadId);
            await Task.Run(() => new IGetRun2().Run2());
    }
}

internal class IGetRun2
{
    public void Run2()
    {
        Console.WriteLine("Run2 " + Thread.CurrentThread.ManagedThreadId);
        throw new Exception("dd");
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑: 看起来我忘了用Task声明每个异步方法而不是void,因此异常处理现在可以预测.我唯一还不知道的是为什么 - 如果我在按钮事件中停止处理异常 - 当捕获异常时Application_ThreadException触发而不是TaskScheduler_UnobservedTaskException

svi*_*ick 7

您的原始代码执行了一些您不应该做的事情:调用async void方法.有问题的异常处理是其中一个原因.

让我们逐个看一下事件处理程序:

  • Application.ThreadException仅处理Winforms UI线程上的异常.异常从未进入UI线程,因此此事件不会触发.
  • TaskScheduler.UnobservedTaskException只处理Task未被观察的s中的异常(即使这样,它们在最终确定时会这样做,如果你没有分配很多内存,这可能需要很长时间).但是未观察到的异常并没有与任何异常相关联Task,所以这也不会触发.
  • AppDomain.CurrentDomain.UnhandledException处理在前两个事件处理程序之后仍被视为未被观察到的所有异常.但它不能用于设置观察到的异常,因此应用程序仍然会终止.这是实际触发的唯一处理程序.

为什么前两个处理程序确实没有火?在你的代码,你有Task.Run(() => new IGetRun().Run1()),这里Run1()是一个async void抛出异常的方法.

当方法中存在未观察到的异常时async void,将在当前同步上下文中重新抛出异常.但由于该方法在线程池线程上运行(因为Task.Run()),因此没有同步上下文.因此,在线程池线程上抛出异常,前两个事件处理程序无法观察到该异常.

正如您已经发现的,此问题的解决async Task方法是await正确使用方法和方法.


在您更新的代码中,Run1()现在是一种async Task方法.那Task是由包装Task.Run()然后await编辑,所以异常转移到Hello() Task.这反过来await来自async void button1_ClickUI线程上的处理程序.

这意味着没有未观察到的Task异常,并且在UI同步上下文中重新抛出异常.在Winforms中,这意味着Application.ThreadException被提升,这正是你所观察到的.