未处理的异常处理程序未被调用以处理Metro / WinRT UI异步void事件处理程序。

13

以下内容是从一款Windows 8 Metro/WinRT应用提取出来的片段,已经被精简到展现异常所需的最少程度:

public class App : Application
{
    public App()
    {
        UnhandledException += (sender, e) => e.Handled = true;
    }
}

public class MainPage : Page
{
    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        throw new NotSupportedException();
    }

    private async void Button_Click_2(object sender, RoutedEventArgs e)
    {
        throw new NotSupportedException();
    }
}

假设有一个Metro UI,其中包含两个按钮和它们各自的点击事件处理程序,唯一的区别是第二个事件处理程序被标记为async

然后,当我单击每个按钮时,我希望在两种情况下都会调用UnhandledException处理程序,因为它们(应该)都是通过UI线程和关联的同步上下文输入的。我的理解是,在async void方法中,任何异常都应该通过最初的同步上下文捕获并“重新抛出”(保留原始堆栈跟踪),这也在Async / Await FAQ中明确说明。

但是,在async情况下,UnhandledException处理程序没有被调用,因此应用程序崩溃了!由于这挑战了我认为非常直观的模型,我需要知道原因!是的,我知道可以将处理程序的主体包装在try { } catch { }中,但我的问题是为什么没有调用最终的UnhandledException处理程序?

为了进一步强调为什么这不合理,考虑以下来自WPF应用程序的几乎相同的截取,该应用程序还使用了async / await并针对.NET Framework 4.5:

public class App : Application
{
    public App()
    {
        DispatcherUnhandledException += (sender, e) => e.Handled = true;
    }
}

public class MainWindow : Window
{
    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        throw new NotSupportedException();
    }

    private async void Button_Click_2(object sender, RoutedEventArgs e)
    {
        throw new NotSupportedException();
    }
}

你有解决这个问题的方法或变通之道吗?我遇到了完全相同的问题——UnhandledException 对于记录错误来说是个严重问题。 - Ben Hoyt
不要让异常逃脱 async void 事件处理程序;即用 try { } catch { } 块包装主体,或将主体作为 lambda 传递给帮助函数 - 该函数会包装 lambda 以避免重复。 - Sean Fausett
Win 8.1的Application.UnhandledException事件没有问题。 - foson
6个回答

5

5

谢谢你的链接,Pierre。不幸的是,它并没有我所谓的答案。我还发现了Application.UnhandledException无法捕获事件,所以我不是唯一对这种行为表示质疑的人。有个小鸟告诉我,这是一个已知的问题,在发布前没有得到修复。如果微软内部的某个人能够回答/评论,我将不胜感激。 - Sean Fausett
这是一个已知的问题,在发布时没有及时修复。那不就是答案吗?你还想了解什么? - Pierre Henri K
一个间接的消息,比如从某个认识的人那里听来的,只是一种谣言,而不是答案。 “如果微软内部的某个人能够回答/评论,我会非常感激。” - Sean Fausett
1
我给出的链接中的答案来自微软的Matt Small。 - Pierre Henri K
但是我在提供的链接中没有看到任何来自Matt(一位升级工程师)的“答案”,只有确认问题存在的信息。 - Sean Fausett

4
如文档所述(来源:http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.application.unhandledexception.aspx):
重要的是要了解 Application.UnhandledException 事件的几个限制。此事件仅用于 XAML 框架遇到的异常。由其他 Windows 运行时组件或未连接到 XAML 框架的应用程序部分遇到的异常不会导致引发此事件。
例如,如果不同的 Windows 组件调用应用程序代码并引发未捕获的异常,则不会引发 UnhandledException 事件。如果应用程序创建工作线程,然后在工作线程上引发异常,则不会引发 UnhandledException 事件。
此对话中指出,检索发生在工作线程中的异常的唯一方法是将它们包装在 try/catch 块中。因此,在我的应用程序中使用以下解决方法:不使用 Task.Run 或等效方法从 UI 执行代码以在工作线程上运行,而是使用此方法:
/// <summary>
/// Runs code in a worker thread and retrieves a related exception if any.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="action">The action.</param>
public static void SafeRun(this DependencyObject target, Action action)
{
    Task task = ThreadPool.RunAsync(o =>
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            /* Call the same error logging logic as in the UnhandledException event handler here. */
        }
    }).AsTask();
    task.Wait();
}

总之,要记录应用程序中的所有错误,您需要:

  1. 订阅 UnhandledException 事件以获取与 XAML 栈相关的错误信息,
  2. 将在工作线程中发生的操作放入 try/catch 块中。

我没有创建任何线程。async void示例事件处理程序在UI线程和相关的同步上下文上运行。是的,解决方法是不让异常逃逸,正如已经说明的那样。 - Sean Fausett

0

从我的角度来看,这里唯一正确的答案被踩了(尝试使用TaskScheduler的UnobservedTaskException事件)

问题在于你正在使用'async void',而异常无法处理。这不是WinRT的限制,而是异步设计行为。您需要深入了解异步以正确实现此处的异常处理。请参阅此文章:http://msdn.microsoft.com/en-us/magazine/jj991977.aspx

当GC将任务作为未观察到的任务收集时,异常会被重新抛出。您可以通过注册TaskScheduler UnobservedTaskException事件来获取它们。注意-需要一些时间才能到达异常,因为它是由垃圾收集器记录的。

通常不要使用'async void',但在UI事件处理程序中必须使用,所以这是你唯一的选择。


你读了你所引用的那篇文章吗?它说的和我在2012年10月23日的评论一样:“使用异步void方法时,没有Task对象,因此从异步void方法抛出的任何异常都将直接在启动异步void方法时处于活动状态的同步上下文上引发。” - Sean Fausett
抱歉,你是对的!我再次测试后发现WinRT立即崩溃了。我们正在使用Caliburn Micro,所以我一开始没有注意到它..也许这可以是一个解决方法:Caliburn接受async Task事件处理程序并记录异常,因此这有助于所有Click等事件。我们使用一种FireAndForget-Trigger代替async void处理程序,该处理程序通过Task.Run在后台启动方法(同步和异步),并负责异常处理。 - Lukas K
1
我们现在可以对更好的未来抱有希望:http://msdn.microsoft.com/en-us/library/windows/apps/dn263110.aspx - 查看**"Surface all errors" feature**部分 - 他们似乎计划更好地处理错误。 - Lukas K

0

这个问题很旧了,但是现在我的UWP应用程序也遇到了类似的问题。有时我必须通过允许异常向上传播来解开调用堆栈。我需要异常到达应用程序的UnhandledException处理程序,但并不总是发生。这个问题包括在非UI线程上抛出异常的明显情况。它还包括一个不明显的情况,其因果关系我尚未确定,在UI线程上抛出异常。

我想出了以下解决方案。我捕获异常,然后将其明确地发布到同步上下文中。由于前面提到的奇怪问题,即使已经在当前同步上下文中运行,我也必须这样做。否则,异常有时无法到达UnhandledException处理程序。

private void Throw( Exception exception )
{
    uiContext.Post( arg => throw exception, null );
    throw new OperationCanceledException();
}

-1
尝试使用TaskScheduler的UnobservedTaskException事件。

1
“async void”不使用任务(task),而是使用同步上下文(synchronization context)。请参阅Async / Await FAQ。 - Sean Fausett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接