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

时间:2013-12-12 11:26:21

标签: c# winforms async-await

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

    Application.ThreadException += Application_ThreadException;
    Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
    AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

然后我在窗体上的嵌套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");
    }
}

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

2 个答案:

答案 0 :(得分:7)

您的原始代码会执行您不应该执行的操作:调用async void方法。有问题的异常处理是其中一个原因。

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

  • Application.ThreadException仅处理Winforms UI线程上的异常。异常从未进入UI线程,因此此事件不会触发。
  • TaskScheduler.UnobservedTaskException仅处理来自Task的未被观察到的异常(即使这样,它们在最终确定时会这样做,如果您没有分配大量内存,则可能需要很长时间)。但是,未观察到的异常与任何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。反过来,它是来自UI线程上的await处理程序的async void button1_Click

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

答案 1 :(得分:0)

我不确定你是否解决了问题? 如果要抓住你的unhandeld例外,你可以这样做。 在Application.Run

之前添加到program.cs
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);

创建2个空格来处理异常数据。

static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
  MessageBox.Show(e.Exception.Message, "Unhandled Thread Exception");
  // here you can log the exception ...
}

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
  MessageBox.Show((e.ExceptionObject as Exception).Message, "Unhandled UI Exception");
  // here you can log the exception ...
}