C# 如何遍历.NET try/catch链以决定生成小型转储
我们的顶级崩溃处理程序遇到了一个混合任务的难题,我们正试图找到一个解决办法。我希望有人有一些想法 我们的工具有一个顶级崩溃处理程序(来自AppDomain的UnhandledException事件),我们用它来用小型转储文件bug报告。它工作得很好。不幸的是,任务在这方面起了作用 我们刚开始使用4.0任务,发现在任务操作执行代码的内部,有一个try/catch,它捕获异常并存储异常,以便传递到任务链。不幸的是,C# 如何遍历.NET try/catch链以决定生成小型转储,c#,task-parallel-library,C#,Task Parallel Library,我们的顶级崩溃处理程序遇到了一个混合任务的难题,我们正试图找到一个解决办法。我希望有人有一些想法 我们的工具有一个顶级崩溃处理程序(来自AppDomain的UnhandledException事件),我们用它来用小型转储文件bug报告。它工作得很好。不幸的是,任务在这方面起了作用 我们刚开始使用4.0任务,发现在任务操作执行代码的内部,有一个try/catch,它捕获异常并存储异常,以便传递到任务链。不幸的是,catch(Exception)的存在使堆栈展开,当我们创建迷你转储时,调用站点丢失。
catch(Exception)
的存在使堆栈展开,当我们创建迷你转储时,调用站点丢失。这意味着我们在崩溃时没有任何局部变量,或者它们已经被收集,等等
异常筛选器似乎是此作业的正确工具。我们可以通过扩展方法将一些任务操作代码封装在过滤器中,在抛出异常时,调用崩溃处理程序代码以使用minidump报告错误。但是,我们不希望对每个异常都这样做,因为在我们自己的代码中可能存在忽略特定异常的try-catch。我们只想在任务中的catch
要处理崩溃报告时才执行它
有没有办法沿着try/catch处理程序的链条走?我想,如果我能做到这一点,我可以向上走去寻找捕获物,直到击中任务,然后如果是真的,就启动碰撞处理程序
(这似乎不太可能,但我想我还是会问。)
或者,如果有人有更好的想法,我很乐意听到
更新
我创建了一个小的示例程序来演示这个问题。很抱歉,我试着把它尽量短一些,但还是很大/
在下面的示例中,您可以#定义USETASK
或#定义USEWORKITEM
(或无)来测试三个选项之一
在非异步情况和USEWORKITEM情况下,生成的minidump是在调用站点构建的,这正是我们需要的。我可以将它加载到VS中(在浏览了一些以找到正确的线程之后),我看到我在调用站点上拍摄了快照。太棒了
在USETASK情况下,快照是从正在清理任务的终结器线程中获取的。这是在抛出异常很久之后的事情,因此此时获取一个小型转储是没有用的。我可以对任务执行Wait(),以使异常得到更快的处理,或者我可以直接访问它的异常,或者我可以从TestCrash本身的包装中创建minidump,但所有这些都存在相同的问题:因为堆栈已解缠到一个或另一个捕获,所以为时已晚
请注意,我特意在TestCrash中添加了一个try/catch,以演示如何正常处理一些异常,以及如何捕获其他异常。USEWORKITEM和非异步案例正是我们所需要的工作方式。任务几乎做对了!如果我能以某种方式使用一个异常过滤器,让我沿着try/catch链(而不是实际展开)走到任务内部的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);
}
如果某个任务引发未处理的异常,听起来您希望创建一个小型转储。在
unobservedtaskeexception
事件期间,堆栈可能尚未展开:
任务
自身的异常筛选器之前运行,因此只有在任务中除任务
获得异常之前没有其他内容处理异常时,才会调用该筛选器
这是一个工作样本。在VB文件中创建名为ExceptionFilter
的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
我运行了C#程序,打开了DMP文件,并检查了调用堆栈。TestCrash
函数在堆栈上(向上几帧),当前行为throw new
仅供参考,我想我会使用
Environment.FailFast()
来处理您的小型转储/终止操作,但这在您的工作流程中可能不起作用。想到两种可能性:
您可以使用来充当调试器,并检测哪个catch
块将捕获异常
您可以将每个“关键任务”Action
/Func
包装在自己的try
/catch
包装中
不过,这两种方法中的任何一种都需要付出相当大的努力。为了回答你的具体问题,我认为不可能用手在堆栈上行走
<强>编辑:更多关于配置API方法:您可能需要考虑,它是为单元测试编写的,但公开了可以在运行时使用的钩子(是通过Type的API编写的库)。另外还有一个关于使用评测API进行代码注入的问题,但是IMO TypeMock会比您自己做省钱。
我在并行计算论坛上发布了一篇文章。Stephen Toub建议的解决方法是在任务主体周围添加一个异常处理程序,用于捕获所有异常和调用Environment.FailFast
。(或者它可以用小型转储文件提交错误报告,等等)
例如:
public static Action FailOnException(this Action original)
{
return () =>
{
try { original(); }
catch(Exception ex) { Environment.FailFast("Unhandled exception", ex); }
};
}
然后,不要写:
Task.Factory.StartNew(action);
你可以写:
Task.Factory.StartNew(action.FailOnException());
因为这保证是默认任务异常处理程序下面的一层,所以您不需要遍历异常处理链,也不需要担心其他代码是否会处理它。(如果捕获并处理了异常,它将不会到达此处理程序。)
或者,因为(正如Gabe在评论中所指出的那样)这将导致最终运行块,所以应该可以(正如我在您的问题中所建议的)添加(
Task.Factory.StartNew(action.FailOnException());