【问题标题】:AppDomain.FirstChanceException and stack overflow exceptionAppDomain.FirstChanceException 和堆栈溢出异常
【发布时间】:2012-05-28 15:40:18
【问题描述】:

我正在使用FirstChanceException 事件来记录有关任何引发的异常的详细信息。

static void Main(string[] args)
{
    AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
    {
        Console.WriteLine("Inside first chance exception.");
    };

    throw new Exception("Exception thrown in main.");
}

这按预期工作。但是,如果在事件处理程序内部抛出异常,则会发生堆栈溢出,因为该事件将递归引发。

static void Main(string[] args)
{
    AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
    {
        throw new Exception("Stackoverflow");
    };

    throw new Exception("Exception thrown in main.");
}

如何处理事件处理程序中发生的异常?

编辑:

有一些答案建议我将代码包装在 try/catch 块中的事件处理程序中,但这不起作用,因为在处理异常之前引发了事件。

static void Main(string[] args)
{
    AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
    {
        try
        {
            throw new Exception("Stackoverflow");
        }
        catch
        {
        }
    };

    throw new Exception("Exception thrown in main.");
}

【问题讨论】:

  • 只使用布尔字段来防止递归。
  • 我不明白你为什么想要这个。第一次机会异常被处理。你到底为什么要扔另一个?
  • 我不是故意扔另一个。如果我尝试记录该错误并在尝试记录该信息时引发异常,会发生什么情况?
  • @nivlam 你不应该首先记录第一次机会异常。如果处理得当,它们就不是错误。
  • @nivlam 在这种情况下,使用调试器会容易得多。 创建一个仅记录第一次机会异常的简单调试器可能会更容易。

标签: c#


【解决方案1】:

这对我有用:

private volatile bool _insideFirstChanceExceptionHandler;    

// ...

AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException;

// ...

private void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs args)
{
    if (_insideFirstChanceExceptionHandler)
    {
        // Prevent recursion if an exception is thrown inside this method
        return;
    }

    _insideFirstChanceExceptionHandler = true;
    try
    {
        // Code which may throw an exception
    }
    catch
    {
        // You have to catch all exceptions inside this method
    }
    finally
    {
        _insideFirstChanceExceptionHandler = false;
    }
}

【讨论】:

  • 这确实是正确的做法,应该标记为接受的答案。您可能需要扩充代码以处理不同的应用程序域,但除此之外,不错的解决方案!
  • 不错的解决方案,你拯救了我的一天
  • @ayhjar 拥有 91 声望。这是关于 SO 的最佳答案之一。干得好,睡裤先生。你应该多参与!
  • 如果同时发生两个异常,这不是会忽略第二个并返回吗?还是 bool 线程是特定的,因为它的易失性或其他什么?
  • 在使用 firstchanceexception 时,有没有办法知道异常是否被 try/catch 块优雅地处理?
【解决方案2】:

尽管这不是一个好方法,但在 VB .NET 中,您可以使用来自 VB 6 的“On Error Resume Next”语句来防止在 FirstChanceException 事件处理程序中触发异常。(我不确定 C# 是否有类似的东西)另外,您应该防止在here 中提到的事件处理程序上进行递归。以下是示例代码,似乎可以按预期工作。

Sub Main(args As String())
    AddHandler AppDomain.CurrentDomain.FirstChanceException, AddressOf FirstChanceExceptionEventHandler
    Throw New Exception("Exception thrown in main.")
End Sub

Private Sub FirstChanceExceptionEventHandler(ByVal source As Object, ByVal e As FirstChanceExceptionEventArgs)
    On Error Resume Next

    Dim frames As StackFrame() = New StackTrace(1).GetFrames()
    Dim currentMethod As MethodBase = MethodBase.GetCurrentMethod()
    If frames IsNot Nothing AndAlso frames.Any(Function(x) x.GetMethod() = currentMethod) Then
        Return
    Else
        Throw New Exception("Stackoverflow")
    End If
End Sub

【讨论】:

    【解决方案3】:

    首先使用方法而不是委托,所以会定义方法名

    然后使用 Environment.StackTrace 检查方法是否已经在堆栈跟踪中

    这是一段代码未经测试

    static void Main(string[] args) 
    { 
        AppDomain.CurrentDomain.FirstChanceException += handleFirstChanceException;
    } 
    
    private void handleFirstChanceException(object sender, EventArgs eventArgs)
    {
        if (Environment.StackTrace.Contains("handleFirstChanceException"))
            return;
    
        // handle
    }
    

    我认为上面的方法行不通,因为它总是包含方法名称,但是如果它出现超过 1 次,你可以计算。 另外,在发布模式下编译时检查它是否没有内联,在这种情况下你有麻烦

    【讨论】:

    • Environment.StackTrace 属性 getter 抛出异常时会发生什么?
    • 为什么要抛出? Doc (msdn.microsoft.com/en-us/library/…) 说它只能抛出 ArgumentOutOfRangeException 但我不能在这种情况下
    • 任何东西都可以抛出OutOfMemoryException。这将触发第一次机会异常,并尝试获取另一个堆栈跟踪,这也将失败,因为您的内存不足。
    • 确实如此,但是如果您的应用程序抛出 OutOfMemoryException,它已经准备好崩溃了……所以它不会因为 OutOfMemory 而崩溃,而是会因为 StackOverflow 而崩溃
    • 我不喜欢“它已经出了问题,所以让它变得更糟”的论点,但Exception.StackTrace 也可能给出其他例外。和你一样,我不知道什么时候可能会抛出 ArgumentOutOfRangeException,但文档中的列表并不是一个详尽的列表。例如,它也没有列出潜在的SecurityException。此外,我认为没有理由假设 Environment.StackTrace 有时不会在内部抛出和捕获异常,这会再次触发 FirstChanceException 事件。
    【解决方案4】:

    您链接的 MSDN 文章提出了一些建议:

    您必须处理 FirstChanceException 事件的事件处理程序中发生的所有异常。否则,将递归引发 FirstChanceException。这可能导致堆栈溢出和应用程序终止。我们建议您将此事件的事件处理程序实现为受约束的执行区域 (CER),以防止与基础架构相关的异常(例如内存不足或堆栈溢出)在处理异常通知时影响虚拟机。

    因此,将您的函数包含在 try/catch 块中,并在该块之前调用 PrepareConstrainedRegion 以避免 OutOfMemory 异常:http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions.aspx

    编辑:好吧,即使使用 try/catch 块,您仍然会遇到递归问题。所以......我猜你只需要调用不会抛出任何异常的安全代码。该事件处理程序似乎很危险,我建议仅将其用于调试目的。

    【讨论】:

    • ASP.NET 的性能有什么问题吗?我的应用程序 ASP.NET 4.6.1 有一个 dump production。仅 20 分钟就有 7000 个例外情况第一次机会例外情况)。我需要安全地记录 first chance exceptions 来研究问题,而不需要 stackoverflow 或 outofmemory 异常
    【解决方案5】:

    一般异常您可以像所有其他异常一样处理,但特别是 StackOverflowOutOfMemory 异常呢,它们无法在 .NET Framework 中处理。

    看这里:How do I prevent and/or handle a StackOverflowException? (C#)

    从 .NET Framework 2.0 版开始,StackOverflowException 对象不能被 try-catch 块和相应的 进程默认终止。因此,建议用户 编写他们的代码来检测和防止堆栈溢出。例如, 如果您的应用程序依赖于递归,请使用计数器或状态 终止递归循环的条件。

    【讨论】:

    • 我不想捕获 stackoverflow 异常。我正在寻找一种方法来防止它发生。
    • @nivlam:为防止它发生,请不要以您调用它的 方式 调用创建堆栈溢出的函数。你在寻找什么解决方案,所以??
    【解决方案6】:

    我认为在异常处理程序中添加另一个 try {} catch (){} 块会有所帮助

    static void Main(string[] args) 
    { 
        AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) => 
        { 
            try {
                throw new Exception("Stackoverflow"); 
            } catch (Exception e)
            {
               // Do something very simple not throwing an exception...
            }
        }; 
    
        throw new Exception("Exception thrown in main."); 
    } 
    

    【讨论】:

    • 这不起作用。在可以在 catch 块中处理异常之前引发 FirstChanceException 事件。
    【解决方案7】:

    手动处理内部异常,例如

    static void Main(string[] args) {
         AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
         {
             try{
             throw new Exception("Stackoverflow");} catch (Exception ex){/*manual handle*/}
         };
          throw new Exception("Exception thrown in main.");
     } 
    

    【讨论】:

      猜你喜欢
      • 2010-11-27
      • 1970-01-01
      • 1970-01-01
      • 2016-02-19
      • 1970-01-01
      • 2017-03-31
      • 2010-10-30
      相关资源
      最近更新 更多