WinRT C#: 无法将未处理的异常保存到存储器

4
我正在处理WinRT技术。如果出现未处理的异常,我想将消息文本写入存储。在 'App.xaml.cs' 中添加了一个事件处理程序,请参见代码。
异常被捕获,但最后一行写文件的代码再次崩溃 ->“异常”!为什么?有任何想法吗?
 public App()
 {
    this.InitializeComponent();
    this.Suspending += OnSuspending;
    this.UnhandledException += App_UnhandledException;
 }

 async void App_UnhandledException(object sender, UnhandledExceptionEventArgs e)
 {
    StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder; 
    StorageFile file= await folder.CreateFileAsync("crash.log",CreationCollisionOption.OpenIfExists);

    await FileIO.AppendTextAsync(file, e.Message);  // <----- crash again -----
 }

感谢您的使用

Sunny


2
我复现了。这个问题很难诊断,AppendTextAsync() 调用失败的方式非常恶劣。我猜你最好避免在这段代码中使用 await - Hans Passant
你认为这里需要使用延迟执行(deferral)吗? - Jim O'Neil
3个回答

4

我一直在思考同样的问题,并在搜索过程中很早就偶然发现了这个。我已经找到了一种方法,希望对其他人也有用。

问题在于await返回UI线程控制权并导致应用程序崩溃。您需要延迟,但实际上没有什么好方法可以获得。

我的解决方案是改用设置存储。我假设大多数想要这样做的人都想要像LittleWatson一样做些什么,因此以下代码是从http://blogs.msdn.com/b/andypennell/archive/2010/11/01/error-reporting-on-windows-phone-7.aspx修改而来,以方便使用:

namespace YourApp
{
    using Windows.Storage;
    using Windows.UI.Popups;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;

    public class LittleWatson
    {
        private const string settingname = "LittleWatsonDetails";
        private const string email = "mailto:?to=you@example.com&subject=YourApp auto-generated problem report&body=";
        private const string extra = "extra", message = "message", stacktrace = "stacktrace";

        internal static void ReportException(Exception ex, string extraData)
        {
            ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always);
            var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values;

            exceptionValues[extra] = extraData;
            exceptionValues[message] = ex.Message;
            exceptionValues[stacktrace] = ex.StackTrace;
        }

        internal async static Task CheckForPreviousException()
        {
            var container = ApplicationData.Current.LocalSettings.Containers;
            try
            {
                var exceptionValues = container[settingname].Values;
                string extraData = exceptionValues[extra] as string;
                string messageData = exceptionValues[message] as string;
                string stacktraceData = exceptionValues[stacktrace] as string;

                var sb = new StringBuilder();
                sb.AppendLine(extraData);
                sb.AppendLine(messageData);
                sb.AppendLine(stacktraceData);

                string contents = sb.ToString();

                SafeDeleteLog();

                if (stacktraceData != null && stacktraceData.Length > 0)
                {
                    var dialog = new MessageDialog("A problem occured the last time you ran this application. Would you like to report it so that we can fix the error?", "Error Report")
                    {
                        CancelCommandIndex = 1,
                        DefaultCommandIndex = 0
                    };

                    dialog.Commands.Add(new UICommand("Send", async delegate
                    {
                        var mailToSend = email.ToString();
                        mailToSend += contents;
                        var mailto = new Uri(mailToSend);
                        await Windows.System.Launcher.LaunchUriAsync(mailto);
                    }));
                    dialog.Commands.Add(new UICommand("Cancel"));

                    await dialog.ShowAsync();
                }
            }
            catch (KeyNotFoundException)
            {
                // KeyNotFoundException will fire if we've not ever had crash data. No worries!
            }
        }

        private static void SafeDeleteLog()
        {
            ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always);
            var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values;

            exceptionValues[extra] = string.Empty;
            exceptionValues[message] = string.Empty;
            exceptionValues[stacktrace] = string.Empty;
        }
    }
}

为了实现它,您需要按照上面链接中的说明进行操作,但是为了确保数据在URL失效时仍然存在,需要执行以下操作:
App.xaml.cs 构造函数(在调用 this.InitializeComponent() 之前):
this.UnhandledException += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here");

如果您已经有了一个UnhandledException方法,您可以在其中抛出对LittleWatson的调用。

如果您使用的是Windows 8.1,您还可以添加一个NavigationFailed调用。这需要在实际页面中进行(通常是MainPage.xaml.cs或任何首先打开的页面):

xx.xaml.cs构造函数(任何给定页面):

rootFrame.NavigationFailed += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here");

最后,您需要询问用户在应用重新打开时是否要发送电子邮件。在您的应用程序的默认页面构造函数中(默认情况下,页面App.xaml.cs 初始化):
this.Loaded += async (s, e) => await LittleWatson.CheckForPreviousException();

如果您已经使用了OnLoad方法,请将此调用添加到其中;否则,请在代码中直接添加。

1
你知道这是否适用于Windows 8和8.1应用程序吗? - Earlz
我无法确认它,因为我身边没有Windows 8.0机器,也没有VS 2012。不过,我已经打开了一个旧的8.0应用程序,LittleWatson代码本身运行良好。然而,8.0似乎没有“NavigationFailed”事件,所以你只能在App.xaml.cs中使用UnhandledException事件调用LittleWatson - rootFrame部分将无法工作。 - Alex Hardwicke

0
在这种情况下,await 可以被大致翻译为“在另一个线程上执行此任务,并在等待其完成时继续您正在进行的操作”。考虑到您的应用程序正在崩溃,您可能不希望它在记录问题之前继续执行。我建议在这种情况下同步运行文件 IO。

0

这可能对于原始问题来说有点晚了,但...

正如@Hans Passant所建议的那样,避免使用await(即同步运行FileIO.AppendTextAsync()),并得到了@Jon的支持,我会选择这种方式,而不是相对较重的代码用于LittleWatson。由于应用程序已经处于某些错误处理状态(这应该是很少发生的情况),因此我不会将由于同步(由于删除等待)引起的任何阻塞作为主要缺点。

除了同步选项之外,以下await实现对我有效:

await FileIO.AppendTextAsync(file, e.Message);更改为:

Task task = LogErrorMessage(file, e.Message)
task.Wait(2000); // adjust the ms value as appropriate

...

private async Task LogErrorMessage(StorageFile file, string errorMessage)
{
    await FileIO.AppendTextAsync(file, errorMessage); // this shouldn't crash in App_UnhandledException as it did before
}

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