未处理未处理的异常处理程序的Metro / WinRT UI异步void事件处理程序

时间:2012-09-10 01:18:32

标签: windows-8 microsoft-metro windows-runtime async-await c#-5.0

请考虑以下内容从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();
    }
}

[WPF同时具有Application DispatcherUnhandledException事件处理程序以及AppDomain UnhandledException事件处理程序有一个细微差别,但您只能在DispatcherUnhandledException中将该异常标记为'processed',它与Metro / WinRT应用程序对齐上面的UnhandledException事件处理程序。]

然后单击每个按钮,DispatcherUnhandledException处理程序确实在两个情况下被调用,正如预期的那样,并且应用程序崩溃。

6 个答案:

答案 0 :(得分:5)

在这里回答:No UnhandledException fired from async event callback

这是WinRT的已知限制。希望它在下次更新时得到修复。

答案 1 :(得分:5)

以下帖子中的解决方案为我工作,只做了一个小改动:我不得不移动AsyncSynchronizationContext.Register();到App.OnLaunched活动

http://www.markermetro.com/2013/01/technical/handling-unhandled-exceptions-with-asyncawait-on-windows-8-and-windows-phone-8/

答案 2 :(得分:3)

如文档中所述(来源:http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.application.unhandledexception.aspx):

  

重要的是要意识到这个问题的一些局限性   Application.UnhandledException事件。此事件仅用于   XAML框架遇到的异常。遇到例外情况   由其他Windows运行时组件或应用程序的一部分   未连接到XAML框架不会导致此事件   被抚养长大。

     

例如,如果其他Windows组件调用   进入应用程序代码并抛出异常并且没有被捕获   不会引发UnhandledException事件。如果应用程序创建   一个工作线程,然后在工作线程上引发一个异常,   

。不会引发UnhandledException事件。

正如在this conversation中所指出的,检索工作线程中发生的异常的唯一方法是将它们包装在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块中的工作线程中发生的包装操作。

答案 3 :(得分:0)

从我的观点来看,唯一正确的回答是在这里被downvoted(尝试TaskScheduler的UnobservedTaskException事件)

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

如果GC收集任务未被观察到,则会重新发生异常。您可以通过注册TaskScheduler UnobservedTaskException事件来获取它们。注意 - 在异常到达之前需要一些时间,因为它用垃圾收集器记录。

一般不要使用'async void',但是在UI事件处理程序中你必须这样做,所以这是你唯一的方法..

答案 4 :(得分:0)

这个问题很旧;尽管如此,现在类似的问题困扰着我的UWP应用。有时,我必须通过允许异常向上传播来释放调用堆栈。我需要异常才能到达应用程序的UnhandledException处理程序,但这并不总是发生。此问题包括抛出非UI线程的明显情况。它也包括一个不明显的情况,引发UI线程。我尚未确定原因。

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

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

答案 5 :(得分:-2)

尝试使用TaskScheduler

的UnobservedTaskException事件