如何防止和/或处理StackOverflowException?


69

我想防止或处理我正在编写的StackOverflowException从调用XslCompiledTransform.Transform方法中得到的a Xsl Editor。问题似乎是用户可以编写一个Xsl script无限递归的,并且只会在调用该Transform方法时炸毁。(也就是说,问题不仅是典型的程序错误,通常是此类异常的原因。)

有没有一种方法可以检测和/或限制允许的递归次数?还是有其他想法可以阻止此代码对我产生负面影响?


1
@William Jockusch您可以采取的一种方法是编写/修改事件探查器,以在堆栈收到呼叫通知时监视堆栈的大小。在ProfilerCallback::_LogCallTrace该CLRProfiler的可能是一个良好的开端,但它似乎并不像一个平凡的事业。通过大卫Browman的博客链接探查源码下载:blogs.msdn.com/b/davbr/archive/2011/02/01/...
forsvarir

4
至少可以这样说,赏金是有问题的。参见meta.stackoverflow.com/questions/296486/…进行讨论。
三胞胎2015年

Answers:


68

从Microsoft:

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

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

您可以做几件事。

  • 编写代码,检查xsl的无限递归并在应用变换(Ugh)之前通知用户。
  • 将XslTransform代码加载到一个单独的进程中(麻烦,但工作较少)。

您可以使用Process类来加载将转换应用到单独流程中的程序集,并在失败后向用户警告失败,而不会终止您的主应用程序。

编辑:我刚刚测试,这是怎么做的:

主流程:

// 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");

ApplyTransform流程:

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);
        }
    }
}

23
在捕获代码引发的StackOverflowException与运行时引发的StackOverflowException之间存在细微的区别。处理您抛出的堆栈溢出完全可以。尽管处理运行时版本非常不同。
JaredPar

3
最初我没有明白您的意思,但是在我的测试代码中添加了一个递归循环后,我明白了您的意思。在这种情况下,您不能捕获UnhandledException ...但是,将其拆分为一个单独的进程将防止应用程序死机,这只会给用户带来不便。
FlySwat

25

注意@WilliamJockusch悬赏中的问题与原始问题有所不同。

这个答案是关于第三方库的一般情况下的StackOverflow以及您可以/不能使用它们的。如果您正在使用XslTransform寻找特殊情况,请参见已接受的答案。


发生堆栈溢出是因为堆栈上的数据超过了某个限制(以字节为单位)。有关如何进行检测的详细信息,请参见此处

我想知道是否有一种通用的方法来跟踪StackOverflowExceptions。换句话说,假设我在代码中的某处有无限递归,但是我不知道在哪里。我想通过某种方式对其进行跟踪,这比遍历所有代码直到看到它发生要容易得多。我不在乎它是多么朴实。

正如我在链接中提到的那样,从静态代码分析中检测堆栈溢出将需要解决无法确定的停止问题。既然我们已经确定没有灵丹妙药,那么我可以向您展示一些我认为有助于解决问题的技巧。

我认为可以用不同的方式来解释这个问题,并且由于我有点无聊:-),所以将其分解为不同的变体。

在测试环境中检测堆栈溢出

基本上,这里的问题是您有一个(有限的)测试环境,并且想在(扩展的)生产环境中检测堆栈溢出。

我没有检测SO本身,而是利用可以设置堆栈深度的事实来解决这个问题。调试器将为您提供所需的所有信息。大多数语言允许您指定堆栈大小或最大递归深度。

基本上,我尝试通过使堆栈深度尽可能小来强制执行SO。如果它没有溢出,我总是可以针对生产环境使其更大(在这种情况下:更安全)。一旦出现堆栈溢出,就可以手动确定它是否为“有效”。

为此,将堆栈大小(在我们的示例中为一个小值)传递给Thread参数,然后看看会发生什么。.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次遇到相同的方法,则发出信号。有一个项目在这里会给你一个良好的开端。

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));
    }
}

4

该答案适用于@WilliamJockusch。

我想知道是否有一种通用的方法来跟踪StackOverflowExceptions。换句话说,假设我在代码中的某处有无限递归,但是我不知道在哪里。我想通过某种方式对其进行跟踪,这比遍历所有代码直到看到它发生要容易得多。我不在乎它是多么朴实。例如,拥有一个我可以激活的模块(可能甚至是从另一个线程激活)会很好,该模块轮询堆栈深度并抱怨它是否达到我认为“太高”的水平。例如,我可能将“太高”设置为600帧,以判断如果堆栈太深,那一定是一个问题。这样的事情是可能的。另一个示例是将代码中每第1000个方法调用记录到调试输出中。这将获得一些证明低档迹象的机会将是相当不错的,而且可能不会使输出恶化得太厉害。关键在于,无论溢出发生在哪里,都不能涉及编写检查。因为整个问题是我不知道那在哪里。最好,解决方案不应该取决于我的开发环境。即,它不应该假设我通过特定的工具集(例如VS)使用C#。

听起来您很想听听一些调试技术来了解此StackOverflow,所以我想我会与您分享一些对您的尝试。

1.内存转储。

Pro的:内存转储是确保消防方式制定出一个堆栈溢出的原因。AC#MVP和我一起对SO进行了故障排除,他在这里继续写博客。

此方法是找出问题的最快方法。

此方法将不需要您按照在日志中看到的步骤重现问题。

反对的:内存转储是非常大的,你必须附加ADPlus的/ procdump的过程。

2.面向方面的编程。

专业人士:这可能是实现代码的最简单方法,该代码可从任何方法检查调用堆栈的大小,而无需在应用程序的每个方法中编写代码。有许多AOP框架可让您在调用之前和之后进行拦截。

会告诉您导致堆栈溢出的方法。

允许您检查StackTrace().FrameCount应用程序中所有方法的入口和出口。

缺点:它将对性能产生影响-每种方法的挂钩都嵌入到IL中,您无法真正“停用”它。

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

3.记录用户活动。

一周前,我试图找出一些难以重现的问题。我发布了此QA用户活动日志记录,遥测(以及全局异常处理程序中的变量)。我得出的结论是一个非常简单的用户操作记录器,用于观察发生任何未处理的异常时如何在调试器中重现问题。

专业人士:您可以随意打开或关闭它(即订阅事件)。

跟踪用户操作不需要拦截所有方法。

您可以算出订阅事件方法的数量比使用AOP简单得多

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

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

缺点:不适合Windows服务,我敢肯定有针对Web应用程序的更好的工具

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

要求您手动浏览日志以重现问题,而不是通过内存转储来直接调试并调试。

 


也许您可以尝试我上面提到的所有技术,以及@atlaste发布的一些技术,并告诉我们您发现哪个最容易/最快速/最脏/最容易在PROD环境中运行等。

无论如何,祝您好运一直追踪下去。


1
在我看来,最简单的方法是逐步执行直到找到它为止,这就是我所做的。一点都不满足,但是满足。无论如何,这很容易回答我的问题。因此赏金。
William Jockusch 2015年

3

如果您的应用程序依赖于3d-party代码(在Xsl脚本中),那么您必须首先确定是否要防御它们中的错误。如果您真的想捍卫,那么我认为您应该在单独的AppDomain中执行容易出现外部错误的逻辑。捕获StackOverflowException不好。

还要检查这个问题


在新的AppDomain中运行代码不会阻止整个过程被StackOverflowException取消。从v2开始,无法在.NET中捕获此类异常。
亚伯,2013年

在控制.NET运行时托管时,可以更改StackOverflowException的处理方式以仅卸载AppDomain而不关闭进程(默认情况下会发生这种情况)。
Govert,2016年

3

今天我有一个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仅适用于堆内存。
阿贝尔2013年

1
由于多种原因,我决定对此答案投反对票。首先,它不会引起stackoverflow。您在堆上分配一个字节数组,该数组被简单地收集。其次,在第一个代码的情况下,编译器足够聪明,可以确定您不使用b它并且没有任何副作用可以删除。我很确定第二个代码将被内联程序拾取,因为它是如此微不足道,因此生成的代码完全相同。第三,第一个赋值的范围是循环的'{'..'}'内容。确实,代码唯一要做的就是永远循环。
atlast

对于无限循环,有些无关的但良好的编程习惯是使用while(true){},而不是某些float基于晦涩的for循环实现。
尼克·默丁

2

使用.NET 4.0,可以将HandleProcessCorruptedStateExceptionsSystem.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");

        }

    }      
}

14
HandleProcessCorruptedStateExceptions不允许您处理StackOverflowException。
布赖恩·拉斯穆森

1

@WilliamJockusch,如果我正确理解了您的担忧,则(从数学的角度来看)不可能总是确定无限递归,因为这将意味着解决Halting问题。要解决这个问题,您需要一种超递归算法(例如,Trial-and-error谓词)或可以进行超级计算的机器(以下部分对此示例进行了说明-可以作为本书的预览))。

从实际的角度来看,您必须知道:

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

请记住,在当前的计算机上,由于多任务处理,该数据极易发生变化,而且我还没有听说过可以执行此任务的软件。

让我知道是否有不清楚的地方。


1
我明白那个。但实际上,如果堆栈在n帧处溢出,则当堆栈达到(例如)n-10帧时,应该有可能破裂。
William Jockusch 2015年

是的,绝对。拥有有限的内存即可。一旦有了内存数据(知道还剩下多少内存/帧就足够了),您可以根据情况暂停或继续操作。至于实现(显然),还有许多其他细节需要考虑,但是有可能知道何时堆栈即将满。
Gentian Kasa

作为实际问题,如果您无权访问堆栈而仅能访问代码,那么您也可以近似决策,但这是另一回事。
Gentian Kasa 2015年

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,如果stacktrace超出了您预设的特定阈值,则可以返回该函数。

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


3
他如何在XSLT中做到这一点?XslCompiledTransform引发此异常。
阿贝尔2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.