如何使用.NET try/catch链来决定生成一个minidump

sco*_*obi 15 c# task-parallel-library

我们遇到了混乱任务与我们的顶级崩溃处理程序,并试图找到一个解决方法.我希望有人有一些想法.

我们的工具有一个顶级崩溃处理程序(来自AppDomain的UnhandledException事件),我们用它来用minidump提交bug报告.它运作得很好.不幸的是,任务在这方面引起了轰动.

我们刚刚开始使用4.0任务,并在Task动作执行代码内部发现了一个try/catch,它抓取异常并存储它以传递任务链.不幸的是,存在catch (Exception)放松堆栈,当我们创建minidump时,调用站点丢失了.这意味着我们在崩溃时没有任何局部变量,或者它们已被收集,等等.

异常过滤器似乎是这项工作的正确工具.我们可以通过扩展方法将一些Task动作代码包装在过滤器中,并且在抛出异常时调用我们的崩溃处理程序代码来报告minidump的错误.但是,我们不希望在每个异常上执行此操作,因为在我们自己的代码中可能存在忽略特定异常的try-catch.如果catchin Task要处理它,我们只想做崩溃报告.

有没有办法走向try/catch处理程序链?我想如果我能做到这一点,我可以向上走,寻找捕获,直到命中任务,然后触发崩溃处理程序,如果是真的.

(这似乎是一个很长的镜头,但我想我还是会问.)

或者,如果有人有任何更好的想法,我很乐意听到他们!

UPDATE

我创建了一个小示例程序来演示这个问题.我道歉,我试图让它尽可能短,但它仍然很大.:/

在下面的示例中,您可以#define USETASK#define USEWORKITEM(或没有)测试三个选项中的一个.

在非异步情况和USEWORKITEM情况下,生成的minidump是在调用站点构建的,正是我们需要的.我可以在VS中加载它(在一些浏览后找到正确的线程),我看到我在呼叫站点拍摄了快照.真棒.

在USETASK案例中,快照从终结器线程中获取,该线程正在清理任务.抛出异常后很长时间,因此在此时抓取一个minidump是没用的.我可以对任务执行Wait()以更快地处理异常,或者我可以直接访问它的Exception,或者我可以在TestCrash本身的包装器中创建minidump,但所有这些仍然有相同的问题:这太晚了,因为堆栈已经解开了一个或另一个.

请注意,我故意在TestCrash中放置一个try/catch来演示我们希望如何正常处理某些异常,以及其他异常被捕获.USEWORKITEM和非异步案例正是我们所需要的.任务几乎做得对!如果我能以某种方式使用异常过滤器让我走向try/catch链(实际上没有展开),直到我点击Task内部的catch,我可以自己做必要的测试,看看是否需要运行崩溃处理程序或不.因此,我原来的问题.

这是样本.

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        AppDomain.CurrentDomain.UnhandledException += (_, __) =>
            {
                using (var stream = File.Create(@"c:\temp\test.dmp"))
                {
                    var process = Process.GetCurrentProcess();
                    MiniDumpWriteDump(
                        process.Handle,
                        process.Id,
                        stream.SafeFileHandle.DangerousGetHandle(),
                        MiniDumpType.MiniDumpWithFullMemory,
                        IntPtr.Zero,
                        IntPtr.Zero,
                        IntPtr.Zero);
                }
                Process.GetCurrentProcess().Kill();
            };
        TaskScheduler.UnobservedTaskException += (_, __) =>
            Debug.WriteLine("If this is called, the call site has already been lost!");

        // must be in separate func to permit collecting the task
        RunTest();

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    static void RunTest()
    {

#if USETASK
        var t = new Task(TestCrash);
        t.RunSynchronously();
#elif USEWORKITEM
        var done = false;
        ThreadPool.QueueUserWorkItem(_ => { TestCrash(); done = true; });
        while (!done) { }
#else
        TestCrash();
#endif
    }

    static void TestCrash()
    {
        try
        {
            new WebClient().DownloadData("http://filenoexist");
        }
        catch (WebException)
        {
            Debug.WriteLine("Caught a WebException!");
        }
        throw new InvalidOperationException("test");
    }

    enum MiniDumpType
    {
        //...
        MiniDumpWithFullMemory = 0x00000002,
        //...
    }

    [DllImport("Dbghelp.dll")]
    static extern bool MiniDumpWriteDump(
        IntPtr hProcess,
        int processId,
        IntPtr hFile,
        MiniDumpType dumpType,
        IntPtr exceptionParam,
        IntPtr userStreamParam,
        IntPtr callbackParam);
}
Run Code Online (Sandbox Code Playgroud)

Gab*_*abe 6

听起来好像你可以将顶级任务包装在异常过滤器(用VB.NET编写?)中,你就可以做你想做的事.由于您的过滤器将在Task自己的异常过滤器之前运行,因此只有在您的任务中没有其他任何内容处理异常但在Task获得它之前,它才会被调用.

这是一个工作样本.ExceptionFilter在VB文件中创建一个用这个调用的VB库项目:

Imports System.IO
Imports System.Diagnostics
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices

Public Module ExceptionFilter
    Private Enum MINIDUMP_TYPE
        MiniDumpWithFullMemory = 2
    End Enum

    <DllImport("dbghelp.dll")>
    Private Function MiniDumpWriteDump(
            ByVal hProcess As IntPtr,
            ByVal ProcessId As Int32,
            ByVal hFile As IntPtr,
            ByVal DumpType As MINIDUMP_TYPE,
            ByVal ExceptionParam As IntPtr,
            ByVal UserStreamParam As IntPtr,
            ByVal CallackParam As IntPtr) As Boolean
    End Function

    Function FailFastFilter() As Boolean
        Dim proc = Process.GetCurrentProcess()
        Using stream As FileStream = File.Create("C:\temp\test.dmp")
            MiniDumpWriteDump(proc.Handle, proc.Id, stream.SafeFileHandle.DangerousGetHandle(),
                              MINIDUMP_TYPE.MiniDumpWithFullMemory, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
        End Using
        proc.Kill()
        Return False
    End Function

    <Extension()>
    Public Function CrashFilter(ByVal task As Action) As Action
        Return Sub()
                   Try
                       task()
                   Catch ex As Exception When _
                       FailFastFilter()
                   End Try
               End Sub
    End Function
End Module
Run Code Online (Sandbox Code Playgroud)

然后创建一个C#项目并添加一个引用ExceptionFilter.这是我用过的程序:

using System;
using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
using ExceptionFilter;

class Program
{
    static void Main()
    {
        new Task(new Action(TestCrash).CrashFilter()).RunSynchronously();
    }

    static void TestCrash()
    {
        try
        {
            new WebClient().DownloadData("http://filenoexist");
        }
        catch (WebException)
        {
            Debug.WriteLine("Caught a WebException!");
        }
        throw new InvalidOperationException("test");
    }
}
Run Code Online (Sandbox Code Playgroud)

我运行了C#程序,打开了DMP文件,并检出了调用堆栈.该TestCrash函数在堆栈上(几帧向上)throw new作为当前行.

仅供参考,我想我会使用Environment.FailFast()你的minidump/kill操作,但这在你的工作流程中可能不会有效.