【问题标题】:Winforms app still crashes after unhandled exception handlerWinforms 应用程序在未处理的异常处理程序后仍然崩溃
【发布时间】:2013-12-12 11:26:20
【问题描述】:

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

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

然后我在表单上的嵌套任务/异步等待上创建一个异常:

尽管处理程序被触发 - 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

【问题讨论】:

  • @JMK 这不能充分回答我的问题
  • 因此使用了“潜在”这个词
  • @JMK 假设您投票结束我的问题,我错了吗?
  • 我注意到您在代码中使用线程的位置,并认为这可能与在一个线程中抛出的异常而不是在另一个线程中被捕获有关,这是常见的问题,因此投票接近。没有其他人投票关闭,所以看起来我错了

标签: c# winforms async-await


【解决方案1】:

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

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

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

为什么前两个处理程序不触发?在您的代码中,您有Task.Run(() => new IGetRun().Run1()),其中Run1() 是一个引发异常的async void 方法。

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

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


在您更新的代码中,Run1() 现在是 async Task 方法。 TaskTask.Run() 解包,然后被awaited 解包,因此异常被转移到Hello() Task。这又是来自 UI 线程上的 async void button1_Click 处理程序的 awaited。

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

【讨论】:

  • 真棒答案 :) 我希望所有关于 SO 的答案都和这个一样完整。
  • @svick 谢谢和+1,但你能回答我附录中的问题吗?假设按钮处理程序中没有 try/catch,并且始终使用异步任务。我没有观察到任何 Task 异常,因此预计 TaskScheduler.UnobservedTaskException 会在一些延迟后运行,但会调用 Application_ThreadException。也许我需要一个新问题...
  • @Jaycee 查看更新。您正在观察 all Task 异常,使用 awaitTask.Run()
【解决方案2】:

我不确定您是否解决了您的问题? 如果关键是要捕获未处理的异常,则可以这样做。 在 Application.Run 之前添加到 program.cs

Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);

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

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 ...
}

【讨论】:

  • 嗨,事实上,我确实解决了处理未处理异常的原始问题,方法是确保异步方法在调用堆栈的签名中一直使用 Task 而不是 Void。我的突出问题是不知道为什么不调用 TaskScheduler_UnobservedTaskException 而不是 Application_ThreadException。
猜你喜欢
  • 2012-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
相关资源
最近更新 更多