如何预防和/或处理 StackOverflowException?

73

我想要防止或处理从调用XslCompiledTransform.Transform方法引起的StackOverflowException,该方法在我编写的Xsl编辑器中使用。问题似乎是用户可以编写无限递归的Xsl脚本,并且在调用Transform方法时它会崩溃。(也就是说,这个问题不仅仅是典型的程序错误,通常情况下这是这种异常的原因。)

是否有一种方式能够检测和/或限制允许的递归次数?或者还有其他想法,使得代码不会只是因此崩溃?


1
@William Jockusch 一个可能的方法是编写/修改一款分析器以监视收到调用通知时栈的大小。CLRProfiler的ProfilerCallback::_LogCallTrace可能是个好的起点,但这似乎并不是一项简单的任务。通过David Browman的博客链接下载分析器源码:http://blogs.msdn.com/b/davbr/archive/2011/02/01/clrprofiler-v4-released.aspx - forsvarir
4
这个赏金至少有问题。请参见http://meta.stackoverflow.com/questions/296486/using-a-bounty-to-ask-a-different-question?cb=1 进行讨论。 - tripleee
10个回答

73

来自微软:

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

我假设异常发生在内部.NET方法中,而不是在您的代码中。

您可以采取一些措施。

  • 编写检查xsl是否存在无限递归并在应用转换之前通知用户的代码(呃)。
  • 将XslTransform代码加载到单独的进程中(Hacky,但更少的工作)。

您可以使用Process类将将应用转换的程序集加载到单独的进程中,并在它死亡时向用户警报失败,而不会杀死主应用程序。

编辑:我刚刚测试了一下,以下是如何操作:

MainProcess:

// This is just an example, obviously you'll want to pass args to this.
Process p1 = new Process();
p1.StartInfo.FileName = "ApplyTransform.exe";
p1.StartInfo.UseShellExecute = false;
p1.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

p1.Start();
p1.WaitForExit();

if (p1.ExitCode == 1)    
   Console.WriteLine("StackOverflow was thrown");

应用转换过程:

class Program
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        throw new StackOverflowException();
    }

    // We trap this, we can't save the process, 
    // but we can prevent the "ILLEGAL OPERATION" window 
    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        if (e.IsTerminating)
        {
            Environment.Exit(1);
        }
    }
}

27
捕捉你的代码抛出的StackOverflowException和运行时抛出的异常之间有微妙的区别。处理你自己代码中的栈溢出是完全可以的。但是,处理运行时版本的异常则非常不同。 - JaredPar
3
一开始我没看懂你的意思,但是在我的测试代码中加入了递归循环后,我明白你的意思了。在那种情况下你无法捕获未处理的异常...不过将其拆分为独立的进程将防止应用程序崩溃,这只会给用户带来轻微的不便。 - FlySwat

31
注意:@WilliamJockusch的悬赏问题和原始问题不同。本答案是关于StackOverflow在第三方库的一般情况下以及你可以/不能使用它们。如果您正在寻找有关XslTransform的特殊情况,请参阅已接受的答案。
Stack overflow是由于栈上的数据超过了某个限制(以字节为单位)而发生的。如何检测这种情况可以在这里找到详细信息。
我想知道是否有一种通用的方法来跟踪StackOverflowExceptions。换句话说,假设我的代码中有无限递归,但我不知道在哪里。我想通过一些比遍历代码更容易的方式来跟踪它。我不在乎它有多么hackish。
正如我在链接中提到的,从静态代码分析中检测堆栈溢出需要解决停机问题,这是不可判定的。既然我们已经确定了“没有银弹”,那么我可以向您展示一些我认为有助于跟踪问题的技巧。
我认为这个问题可以有不同的解释方式,既然我有点无聊,我会将其分解成不同的变体。
在测试环境中检测栈溢出
基本问题是你有一个(有限的)测试环境,并希望在(扩展的)生产环境中检测到堆栈溢出。我通过利用可以设置堆栈深度的事实来解决这个问题,而不是检测 SO 本身。调试器会提供您所需的所有信息。大多数语言允许您指定堆栈大小或最大递归深度。基本上,我尝试通过尽可能减小堆栈深度来强制发生 SO。如果它不溢出,我总是可以使它更大(=在这种情况下:更安全)以适应生产环境。一旦发生堆栈溢出,您可以手动决定它是否是“有效”的。为此,将堆栈大小(在我们的情况下:一个小值)传递给线程参数,然后观察发生了什么。.NET 中的默认堆栈大小为 1 MB,我们将使用一个远远较小的值:
class StackOverflowDetector
{
    static int Recur()
    {
        int variable = 1;
        return variable + Recur();
    }

    static void Start()
    {
        int depth = 1 + Recur();
    }

    static void Main(string[] args)
    {
        Thread t = new Thread(Start, 1);
        t.Start();
        t.Join();
        Console.WriteLine();
        Console.ReadLine();
    }
}

注意:我们还将使用以下代码。

一旦堆栈溢出,您可以将其设置为更大的值,直到获得一个合理的SO。

在SO之前创建异常

StackOverflowException是不可捕获的。这意味着当发生错误时,你没有太多事情可以做。因此,如果您认为代码中必定会出现问题,您可以在某些情况下自己创建异常。您所需要的唯一信息就是当前的堆栈深度;不需要计数器,您可以使用 .NET 中的实际值:

class StackOverflowDetector
{
    static void CheckStackDepth()
    {
        if (new StackTrace().FrameCount > 10) // some arbitrary limit
        {
            throw new StackOverflowException("Bad thread.");
        }
    }

    static int Recur()
    {
        CheckStackDepth();
        int variable = 1;
        return variable + Recur();
    }

    static void Main(string[] args)
    {
        try
        {
            int depth = 1 + Recur();
        }
        catch (ThreadAbortException e)
        {
            Console.WriteLine("We've been a {0}", e.ExceptionState);
        }
        Console.WriteLine();
        Console.ReadLine();
    }
}

注意,这种方法也适用于处理使用回调机制的第三方组件。唯一需要的是你能够拦截堆栈跟踪中的某些调用。 在单独的线程中检测 你明确建议过这个方法,所以这里就介绍一下吧。
你可以尝试在单独的线程中检测SO,但这可能没有什么用。堆栈溢出可能会快速发生,甚至在你进行上下文切换之前。这意味着这种机制根本不可靠... 我不建议实际使用它。虽然构建它很有趣,所以这里是代码 :-)
class StackOverflowDetector
{
    static int Recur()
    {
        Thread.Sleep(1); // simulate that we're actually doing something :-)
        int variable = 1;
        return variable + Recur();
    }

    static void Start()
    {
        try
        {
            int depth = 1 + Recur();
        }
        catch (ThreadAbortException e)
        {
            Console.WriteLine("We've been a {0}", e.ExceptionState);
        }
    }

    static void Main(string[] args)
    {
        // Prepare the execution thread
        Thread t = new Thread(Start);
        t.Priority = ThreadPriority.Lowest;

        // Create the watch thread
        Thread watcher = new Thread(Watcher);
        watcher.Priority = ThreadPriority.Highest;
        watcher.Start(t);

        // Start the execution thread
        t.Start();
        t.Join();

        watcher.Abort();
        Console.WriteLine();
        Console.ReadLine();
    }

    private static void Watcher(object o)
    {
        Thread towatch = (Thread)o;

        while (true)
        {
            if (towatch.ThreadState == System.Threading.ThreadState.Running)
            {
                towatch.Suspend();
                var frames = new System.Diagnostics.StackTrace(towatch, false);
                if (frames.FrameCount > 20)
                {
                    towatch.Resume();
                    towatch.Abort("Bad bad thread!");
                }
                else
                {
                    towatch.Resume();
                }
            }
        }
    }
}

请在调试器中运行此代码并享受其带来的乐趣。
利用堆栈溢出的特性
你问题的另一种解释是:“哪些代码可能会导致堆栈溢出异常?”显然,答案是:所有递归代码。对于每个代码片段,您可以进行一些手动分析。
也可以使用静态代码分析来确定这一点。您需要做的是反编译所有方法并找出它们是否包含无限递归。以下是可为您执行此操作的一些代码:
// A simple decompiler that extracts all method tokens (that is: call, callvirt, newobj in IL)
internal class Decompiler
{
    private Decompiler() { }

    static Decompiler()
    {
        singleByteOpcodes = new OpCode[0x100];
        multiByteOpcodes = new OpCode[0x100];
        FieldInfo[] infoArray1 = typeof(OpCodes).GetFields();
        for (int num1 = 0; num1 < infoArray1.Length; num1++)
        {
            FieldInfo info1 = infoArray1[num1];
            if (info1.FieldType == typeof(OpCode))
            {
                OpCode code1 = (OpCode)info1.GetValue(null);
                ushort num2 = (ushort)code1.Value;
                if (num2 < 0x100)
                {
                    singleByteOpcodes[(int)num2] = code1;
                }
                else
                {
                    if ((num2 & 0xff00) != 0xfe00)
                    {
                        throw new Exception("Invalid opcode: " + num2.ToString());
                    }
                    multiByteOpcodes[num2 & 0xff] = code1;
                }
            }
        }
    }

    private static OpCode[] singleByteOpcodes;
    private static OpCode[] multiByteOpcodes;

    public static MethodBase[] Decompile(MethodBase mi, byte[] ildata)
    {
        HashSet<MethodBase> result = new HashSet<MethodBase>();

        Module module = mi.Module;

        int position = 0;
        while (position < ildata.Length)
        {
            OpCode code = OpCodes.Nop;

            ushort b = ildata[position++];
            if (b != 0xfe)
            {
                code = singleByteOpcodes[b];
            }
            else
            {
                b = ildata[position++];
                code = multiByteOpcodes[b];
                b |= (ushort)(0xfe00);
            }

            switch (code.OperandType)
            {
                case OperandType.InlineNone:
                    break;
                case OperandType.ShortInlineBrTarget:
                case OperandType.ShortInlineI:
                case OperandType.ShortInlineVar:
                    position += 1;
                    break;
                case OperandType.InlineVar:
                    position += 2;
                    break;
                case OperandType.InlineBrTarget:
                case OperandType.InlineField:
                case OperandType.InlineI:
                case OperandType.InlineSig:
                case OperandType.InlineString:
                case OperandType.InlineTok:
                case OperandType.InlineType:
                case OperandType.ShortInlineR:
                    position += 4;
                    break;
                case OperandType.InlineR:
                case OperandType.InlineI8:
                    position += 8;
                    break;
                case OperandType.InlineSwitch:
                    int count = BitConverter.ToInt32(ildata, position);
                    position += count * 4 + 4;
                    break;

                case OperandType.InlineMethod:
                    int methodId = BitConverter.ToInt32(ildata, position);
                    position += 4;
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            result.Add((MethodBase)module.ResolveMember(methodId, mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes));
                        }
                        else
                        {
                            result.Add((MethodBase)module.ResolveMember(methodId, mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments()));
                        }
                    }
                    catch { } 
                    break;
                

                default:
                    throw new Exception("Unknown instruction operand; cannot continue. Operand type: " + code.OperandType);
            }
        }
        return result.ToArray();
    }
}

class StackOverflowDetector
{
    // This method will be found:
    static int Recur()
    {
        CheckStackDepth();
        int variable = 1;
        return variable + Recur();
    }

    static void Main(string[] args)
    {
        RecursionDetector();
        Console.WriteLine();
        Console.ReadLine();
    }

    static void RecursionDetector()
    {
        // First decompile all methods in the assembly:
        Dictionary<MethodBase, MethodBase[]> calling = new Dictionary<MethodBase, MethodBase[]>();
        var assembly = typeof(StackOverflowDetector).Assembly;

        foreach (var type in assembly.GetTypes())
        {
            foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).OfType<MethodBase>())
            {
                var body = member.GetMethodBody();
                if (body!=null)
                {
                    var bytes = body.GetILAsByteArray();
                    if (bytes != null)
                    {
                        // Store all the calls of this method:
                        var calls = Decompiler.Decompile(member, bytes);
                        calling[member] = calls;
                    }
                }
            }
        }

        // Check every method:
        foreach (var method in calling.Keys)
        {
            // If method A -> ... -> method A, we have a possible infinite recursion
            CheckRecursion(method, calling, new HashSet<MethodBase>());
        }
    }

现在,一个方法循环包含递归并不意味着一定会发生堆栈溢出 - 这只是堆栈溢出异常最可能的前提条件。简而言之,这意味着这段代码将确定堆栈溢出可能发生的代码块,这应该大大缩小了大部分代码。

其他方法

还有一些其他方法可以尝试,我没有在这里描述。

  1. 通过托管CLR进程并处理它来处理堆栈溢出。请注意,您仍然无法“捕获”它。
  2. 更改所有IL代码,构建另一个DLL,在递归上添加检查。是的,这是完全可能的(我以前实现过);它只是很难,并且涉及大量代码才能正确地完成。
  3. 使用.NET分析API捕获所有方法调用,并使用它来找出堆栈溢出。例如,您可以实现检查,如果在调用树中遇到相同的方法X次,就会发出信号。有一个项目clrprofiler将为您提供一个起点。

另一种捕获SO异常的方法是这个(正如答案中指出的那样有风险):https://blog.adamfurmanek.pl/2018/04/07/handling-stack-overflow-exception-in-c-with-veh/ - hB0

9
我建议在XmlWriter对象周围创建一个包装器,以便它可以计算WriteStartElement / WriteEndElement的调用次数,如果您将标签数量限制为某个数字(例如100),则可以抛出不同的异常,例如InvalidOperation。这样应该可以解决大多数情况下的问题。
public class LimitedDepthXmlWriter : XmlWriter
{
    private readonly XmlWriter _innerWriter;
    private readonly int _maxDepth;
    private int _depth;

    public LimitedDepthXmlWriter(XmlWriter innerWriter): this(innerWriter, 100)
    {
    }

    public LimitedDepthXmlWriter(XmlWriter innerWriter, int maxDepth)
    {
        _maxDepth = maxDepth;
        _innerWriter = innerWriter;
    }

    public override void Close()
    {
        _innerWriter.Close();
    }

    public override void Flush()
    {
        _innerWriter.Flush();
    }

    public override string LookupPrefix(string ns)
    {
        return _innerWriter.LookupPrefix(ns);
    }

    public override void WriteBase64(byte[] buffer, int index, int count)
    {
        _innerWriter.WriteBase64(buffer, index, count);
    }

    public override void WriteCData(string text)
    {
        _innerWriter.WriteCData(text);
    }

    public override void WriteCharEntity(char ch)
    {
        _innerWriter.WriteCharEntity(ch);
    }

    public override void WriteChars(char[] buffer, int index, int count)
    {
        _innerWriter.WriteChars(buffer, index, count);
    }

    public override void WriteComment(string text)
    {
        _innerWriter.WriteComment(text);
    }

    public override void WriteDocType(string name, string pubid, string sysid, string subset)
    {
        _innerWriter.WriteDocType(name, pubid, sysid, subset);
    }

    public override void WriteEndAttribute()
    {
        _innerWriter.WriteEndAttribute();
    }

    public override void WriteEndDocument()
    {
        _innerWriter.WriteEndDocument();
    }

    public override void WriteEndElement()
    {
        _depth--;

        _innerWriter.WriteEndElement();
    }

    public override void WriteEntityRef(string name)
    {
        _innerWriter.WriteEntityRef(name);
    }

    public override void WriteFullEndElement()
    {
        _innerWriter.WriteFullEndElement();
    }

    public override void WriteProcessingInstruction(string name, string text)
    {
        _innerWriter.WriteProcessingInstruction(name, text);
    }

    public override void WriteRaw(string data)
    {
        _innerWriter.WriteRaw(data);
    }

    public override void WriteRaw(char[] buffer, int index, int count)
    {
        _innerWriter.WriteRaw(buffer, index, count);
    }

    public override void WriteStartAttribute(string prefix, string localName, string ns)
    {
        _innerWriter.WriteStartAttribute(prefix, localName, ns);
    }

    public override void WriteStartDocument(bool standalone)
    {
        _innerWriter.WriteStartDocument(standalone);
    }

    public override void WriteStartDocument()
    {
        _innerWriter.WriteStartDocument();
    }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        if (_depth++ > _maxDepth) ThrowException();

        _innerWriter.WriteStartElement(prefix, localName, ns);
    }

    public override WriteState WriteState
    {
        get { return _innerWriter.WriteState; }
    }

    public override void WriteString(string text)
    {
        _innerWriter.WriteString(text);
    }

    public override void WriteSurrogateCharEntity(char lowChar, char highChar)
    {
        _innerWriter.WriteSurrogateCharEntity(lowChar, highChar);
    }

    public override void WriteWhitespace(string ws)
    {
        _innerWriter.WriteWhitespace(ws);
    }

    private void ThrowException()
    {
        throw new InvalidOperationException(string.Format("Result xml has more than {0} nested tags. It is possible that xslt transformation contains an endless recursive call.", _maxDepth));
    }
}

6
这篇回答是给@WilliamJockusch的。
我想知道是否有一种通用的方法来跟踪StackOverflowExceptions。换句话说,假设我的代码中存在无限递归,但我不知道在哪里。我想通过某种比遍历代码更容易的方式来追踪它。我不在乎这种方法有多么“hackish”。例如,我希望有一个模块可以激活,甚至可以从另一个线程中轮询堆栈深度,并在达到我认为“太高”的级别时发出警告。例如,我可能会将“太高”设置为600帧,认为如果堆栈太深,那肯定是有问题的。是否有类似的方法?另一个例子是将我的代码中每1000个方法调用记录到调试输出中。这样做的机会很大能够得到溢出的证据,而且不太可能使输出崩溃。关键是它不能涉及到在溢出发生的地方编写检查。因为整个问题就是我不知道在哪里。最好的解决方案不应该依赖于我的开发环境是什么样子的;即,它不应该假定我使用特定工具集(例如VS)的C#。

听起来你很想了解一些调试技巧来捕获这个StackOverflow,所以我想分享一些你可以尝试的方法。

1.内存转储

优点: 内存转储是确定Stack Overflow原因的最可靠方法。一位C# MVP和我一起解决了一个Stack Overflow问题,他在博客中写道这里
这种方法是追踪问题的最快方法。
这种方法不需要您通过遵循日志中看到的步骤来重现问题。 缺点: 内存转储非常大,您必须附加AdPlus / procdump进程。

2.面向方面编程

优点: 这可能是您实现在任何方法中检查调用堆栈大小的代码而不编写应用程序中每个方法的代码的最简单方法。有一堆AOP框架允许您拦截调用前后。
将告诉您导致Stack Overflow的方法。
允许您在应用程序中的所有方法的入口和出口处检查StackTrace().FrameCount缺点: 它会对性能产生影响——钩子嵌入到每个方法的IL中,你不能真正地将其“停用”。

它在某种程度上取决于你的开发环境工具集。

3. 记录用户活动。

一周前,我试图追踪几个难以重现的问题。我发布了这个 QA User Activity Logging, Telemetry (and Variables in Global Exception Handlers) 。我得出的结论是一个非常简单的用户操作记录器,可以在任何未处理的异常发生时查看如何在调试器中重现问题。

优点: 你可以随意打开或关闭它(即订阅事件)。

跟踪用户操作不需要拦截每个方法。

你可以更简单地计算方法订阅的事件数,远比AOP简单。

日志文件相对较小,侧重于你需要执行哪些操作才能重现问题。

它可以帮助你了解用户如何使用你的应用程序。

缺点: 不适用于Windows服务,我确信有更好的工具适用于Web应用程序

不一定告诉你导致堆栈溢出的方法。

需要手动步进日志以重现问题,而不是使用内存转储文件,可以直接获取并进行调试。

 


也许你可以尝试上述所有技术以及@atlaste发布的一些技术,并告诉我们哪些技术对于在PROD环境中运行最容易/最快/最脏/最可接受等等。

无论如何,祝你好运追踪这个SO。


1
在我的情况下,最简单的方法似乎是一步一步地找到它,这就是我所做的。虽然不太令人满意,但事实就是如此。无论如何,这是迄今为止最好的回答我的问题的尝试;因此我设置了悬赏。 - William Jockusch

3

如果您的应用程序依赖于第三方代码(在XSL脚本中),那么您首先需要决定是否要防止其中的错误。

如果您真的想防止这些错误,那么我认为您应该在单独的AppDomains中执行容易遭受外部错误的逻辑。捕获StackOverflowException不是一个好方法。

还可以查看这个问题


在新的AppDomain中运行代码并不能防止整个进程被StackOverflowException关闭。从.NET v2开始,这种异常无法在.NET中捕获。 - Abel
当控制.NET运行时托管时,可以更改StackOverflowException的处理方式,仅卸载AppDomain而不是默认情况下终止进程。 - Govert

2

我今天遇到了一个stackoverflow错误,读了一些你的帖子后决定帮助垃圾回收器。

我以前有一个类似这样的近乎无限循环:

    class Foo
    {
        public Foo()
        {
            Go();
        }

        public void Go()
        {
            for (float i = float.MinValue; i < float.MaxValue; i+= 0.000000000000001f)
            {
                byte[] b = new byte[1]; // Causes stackoverflow
            }
        }
    }

相反,让资源像这样超出范围:

class Foo
{
    public Foo()
    {
        GoHelper();
    }

    public void GoHelper()
    {
        for (float i = float.MinValue; i < float.MaxValue; i+= 0.000000000000001f)
        {
            Go();
        }
    }

    public void Go()
    {
        byte[] b = new byte[1]; // Will get cleaned by GC
    }   // right now
}

它对我有效,希望能帮到其他人。


2
这是编写需要大量堆栈空间的方法的好解决方案(字节数组在堆栈上分配),以便在每次调用后清除堆栈。但是,它不是垃圾收集器,而是堆栈展开(退出Go方法后)导致堆栈内存再次可用。GC仅适用于堆内存。 - Abel
1
我决定因为几个原因对这个答案进行负投票。首先,它不会导致堆栈溢出。你在堆上分配了一个字节数组,它只是被收集了。其次,在第一段代码的情况下,编译器足够聪明,可以发现你没有使用 b 并且它没有任何副作用可以移除。我非常确定第二段代码将被内联程序捕获,因为它是如此微不足道的主体,从而产生完全相同的代码。第三,第一个赋值的范围是循环的 '{'..'}' 内容。实际上,代码唯一做的事情就是永远循环。 - atlaste
有点不相关,但是好的编程实践是使用 while(true){} 来实现无限循环,而不是一些模糊的基于 floatfor 循环实现。 - Nick Mertin

1

@WilliamJockusch,如果我理解正确您的关注点,从数学角度来看,不可能总是将无限递归识别出来,因为这意味着要解决停机问题。要解决它,您需要一个超递归算法(例如试错谓词),或者一台可以进行超级计算的机器(例如在本书下一节中介绍的示例)。请注意,内容保留HTML标签。

从实际角度来看,您需要知道:

  • 在给定时间内您还剩下多少堆栈内存
  • 在给定时间内,您的递归方法将需要多少堆栈内存以获得特定输出。

请记住,由于多任务处理,当前计算机上的数据是极其可变的,我没有听说过有软件可以完成这项任务。

如果有不清楚的地方,请告诉我。


1
我理解这一点。但实际上,如果堆栈在n帧时溢出,当堆栈达到(比如说)n-10帧时就应该有可能中断。 - William Jockusch
是的,绝对没问题。有限的内存量可以做到这一点。一旦您拥有了内存数据(知道还剩多少内存/帧应该就足够了),您可以根据情况停止或继续执行。至于实现方面(显然),还有许多其他细节需要考虑,但是可以知道堆栈几乎已满。 - Gentian Kasa
作为实际问题,如果你无法访问堆栈但只能访问代码,你也可以近似决策,但这是另一回事。 - Gentian Kasa

1

使用.NET 4.0,您可以将HandleProcessCorruptedStateExceptions属性从System.Runtime.ExceptionServices添加到包含try/catch块的方法中。这真的起作用了!也许不推荐,但是有效。

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.ExceptionServices;

namespace ExceptionCatching
{
    public class Test
    {
        public void StackOverflow()
        {
            StackOverflow();
        }

        public void CustomException()
        {
            throw new Exception();
        }

        public unsafe void AccessViolation()
        {
            byte b = *(byte*)(8762765876);
        }
    }

    class Program
    {
        [HandleProcessCorruptedStateExceptions]
        static void Main(string[] args)
        {
            Test test = new Test();
            try {
                //test.StackOverflow();
                test.AccessViolation();
                //test.CustomException();
            }
            catch
            {
                Console.WriteLine("Caught.");
            }

            Console.WriteLine("End of program");

        }

    }      
}

15
HandleProcessCorruptedStateExceptions无法处理StackOverflowException。 - Brian Rasmussen

0
看起来除了启动另一个进程,似乎没有处理StackOverflowException的方法。在有人问之前,我尝试使用AppDomain,但那并不起作用:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

namespace StackOverflowExceptionAppDomainTest
{
    class Program
    {
        static void recrusiveAlgorithm()
        {
            recrusiveAlgorithm();
        }
        static void Main(string[] args)
        {
            if(args.Length>0&&args[0]=="--child")
            {
                recrusiveAlgorithm();
            }
            else
            {
                var domain = AppDomain.CreateDomain("Child domain to test StackOverflowException in.");
                domain.ExecuteAssembly(Assembly.GetEntryAssembly().CodeBase, new[] { "--child" });
                domain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) =>
                {
                    Console.WriteLine("Detected unhandled exception: " + e.ExceptionObject.ToString());
                };
                while (true)
                {
                    Console.WriteLine("*");
                    Thread.Sleep(1000);
                }
            }
        }
    }
}

如果您最终使用了独立进程的解决方案,我建议您使用Process.ExitedProcess.StandardOutput并自行处理错误,以给用户提供更好的体验。

-2
您可以每隔几次调用读取此属性Environment.StackTrace,如果堆栈跟踪超过预设的特定阈值,则可以返回该函数。

您还应尝试使用循环替换一些递归函数。


3
他如何在XSLT中做到这一点?抛出此异常的是XslCompiledTransform。 - Abel

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