Как мне предотвратить и / или обработать исключение StackOverflowException?

StackOverflow https://stackoverflow.com/questions/206820

Вопрос

Я хотел бы либо предотвратить, либо справиться с StackOverflowException это я получаю от звонка в XslCompiledTransform.Transform метод в рамках Xsl Editor Я пишу.Проблема, по-видимому, заключается в том, что пользователь может написать Xsl script это бесконечно рекурсивно, и это просто взрывается при вызове Transform способ.(То есть проблема заключается не просто в типичной программной ошибке, которая обычно является причиной такого исключения.)

Есть ли способ определить и / или ограничить количество разрешенных рекурсий?Или какие-нибудь другие идеи, как уберечь этот код от того, чтобы он просто взорвался на мне?

Это было полезно?

Решение

От Microsoft:

Начиная с .NET Framework версия 2.0, исключение StackOverflowException объект не может быть перехвачен блоком try-catch , и соответствующий процесс завершается по умолчанию.Следовательно, пользователям рекомендуется писать свой код для обнаружения и предотвращения переполнения стека .Например, если ваше приложение зависит от рекурсии, используйте счетчик или условие состояния, чтобы завершить рекурсивный цикл.

Я предполагаю, что исключение происходит внутри внутреннего метода .NET, а не в вашем коде.

Ты можешь сделать пару вещей.

  • Напишите код, который проверяет xsl на бесконечную рекурсию и уведомляет пользователя перед применением преобразования (тьфу).
  • Загрузите код 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);
        }
    }
}

Другие советы

ПРИМЕЧАНИЕ Вопрос в баунти от @WilliamJockusch и первоначальный вопрос отличаются.

Этот ответ посвящен StackOverflow в общем случае сторонних библиотек и тому, что вы можете / не можете с ними делать.Если вы ищете особый случай с XslTransform, смотрите Принятый ответ.


Переполнение стека происходит из-за того, что данные в стеке превышают определенный предел (в байтах).Подробную информацию о том, как работает это обнаружение, можно найти здесь здесь.

Мне интересно, есть ли общий способ отследить StackOverflowExceptions.Другими словами, предположим, у меня есть бесконечная рекурсия где-то в моем коде, но я понятия не имею, где именно.Я хочу отследить это каким-нибудь способом, который проще, чем пошаговое выполнение кода повсюду, пока я не увижу, что это происходит.Меня не волнует, насколько это банально.

Как я упоминал в ссылке, обнаружение переполнения стека в результате статического анализа кода потребовало бы решения проблемы остановки, которая заключается в неразрешимый.Теперь, когда мы установили, что серебряной пули не существует, Я могу показать вам несколько приемов, которые, как мне кажется, помогут выявить проблему.

Я думаю, что этот вопрос можно интерпретировать по-разному, и поскольку мне немного скучно :-), я разберу его на разные варианты.

Обнаружение переполнения стека в тестовой среде

По сути, проблема здесь заключается в том, что у вас есть (ограниченная) тестовая среда и вы хотите обнаружить переполнение стека в (расширенной) производственной среде.

Вместо того чтобы обнаруживать SO сам по себе, я решаю эту проблему, используя тот факт, что глубина стека может быть задана.Отладчик предоставит вам всю необходимую информацию.Большинство языков позволяют указать размер стека или максимальную глубину рекурсии.

В принципе, я пытаюсь принудительно использовать SO, делая глубину стека как можно меньше.Если он не переполняется, я всегда могу сделать его больше (= в данном случае:безопаснее) для производственной среды.В тот момент, когда вы получаете переполнение стека, вы можете вручную решить, является ли оно "допустимым" или нет.

Для этого передаем размер стека (в нашем случае:небольшое значение) к параметру потока и посмотрите, что произойдет.Размер стека по умолчанию в .NET равен 1 МБ, мы собираемся использовать меньшее значение:

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, которое имеет смысл.

Создание исключений перед вами, ЧТОБЫ

Тот Самый 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 profiling API для захвата всех вызовов методов и используйте это для определения переполнения стека.Например, вы можете реализовать проверки того, что если вы сталкиваетесь с одним и тем же методом X раз в вашем дереве вызовов, вы подаете сигнал.Есть один проект здесь это даст вам фору.

Я бы предложил создать оболочку вокруг объекта 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));
    }
}

Этот ответ предназначен для @WilliamJockusch.

Мне интересно, есть ли общий способ отследить StackOverflowExceptions.Другими словами, предположим, что у меня есть бесконечная рекурсия где-то в моем коде, но я понятия не имею, где именно.Я хочу отследить это каким-нибудь способом, который проще, чем пошаговое выполнение кода повсюду, пока я не увижу, что это происходит.Меня не волнует, насколько это халтурно .Например, было бы здорово иметь модуль, который я мог бы активировать, возможно, даже из другого потока, который опрашивал стек глубина и жаловался, если он достигал уровня, который я считал "слишком высоким". Для например, я мог бы установить "too high" равным 600 кадрам, полагая, что если стек слишком глубокий, это должно быть проблемой.Возможно ли что-то подобное .Другим примером может быть запись каждого 1000-го вызова метода в моем коде в выходные данные отладки.Шансы, что это будет сделать некоторые свидетельство overlow будет очень хорошо, и это, скорее всего, не будет взорвать тоже плохо выходной.Ключевым моментом является то, что это не может включать написание проверки везде, где происходит переполнение.Потому что весь проблема в том, что я не знаю, где это.Предпочтительно решение не должно зависеть от того, как выглядит моя среда разработки;т.е. не следует предполагать, что я использую C # через определенный набор инструментов (напримерПРОТИВ).

Похоже, вам не терпится услышать некоторые методы отладки, чтобы перехватить этот StackOverflow, поэтому я подумал, что поделюсь парой, чтобы вы попробовали.

1.Дампы памяти.

Профессионалы:Дампы памяти - это верный способ устранить причину переполнения стека.Мы с C # MVP вместе работали над устранением неполадок в SO, и он написал об этом в своем блоге здесь.

Этот метод - самый быстрый способ выявить проблему.

Этот метод не потребует от вас воспроизведения проблем, выполнив шаги, указанные в журналах.

Мошенник:Дампы памяти очень большие, и вы должны подключить ADPlus / procdump к процессу.

2.Аспектно-ориентированное программирование.

Профессионалы:Вероятно, это самый простой способ для вас реализовать код, который проверяет размер стека вызовов из любого метода без написания кода в каждом методе вашего приложения.Существует множество Фреймворки AOP это позволяет вам перехватывать звонки до и после них.

Расскажет вам о методах, которые вызывают переполнение стека.

Позволяет вам проверить StackTrace().FrameCount при входе и выходе всех методов в вашем приложении.

Мошенник:Это повлияет на производительность - хукеры встроены в IL для каждого метода, и вы не можете на самом деле "деактивировать" его.

Это в некоторой степени зависит от набора инструментов вашей среды разработки.

3.Ведение журнала действий пользователя.

Неделю назад я пытался разобраться с несколькими трудно воспроизводимыми проблемами.Я опубликовал этот QA Ведение журнала активности пользователей, телеметрия (и переменные в глобальных обработчиках исключений) .Вывод, к которому я пришел, заключался в действительно простом регистраторе действий пользователя, позволяющем увидеть, как воспроизводить проблемы в отладчике при возникновении любого необработанного исключения.

Профессионалы:Вы можете включать или выключать его по своему желанию (то есть подписываться на события).

Отслеживание действий пользователя не требует перехвата всех методов.

Вы также можете подсчитать количество подписанных методов events гораздо проще, чем с AOP.

Файлы журналов относительно невелики и сосредоточены на том, какие действия вам необходимо выполнить, чтобы воспроизвести проблему.

Это может помочь вам понять, как пользователи используют ваше приложение.

Мошенник:Не подходит для службы Windows и я уверен, что для веб-приложений есть лучшие инструменты, подобные этому.

Не делает обязательно расскажу вам о методах, которые вызывают переполнение стека.

Требуется, чтобы вы просматривали журналы, вручную воспроизводящие проблемы, а не дамп памяти, где вы можете получить его и сразу отладить.

 


Может быть, вы могли бы попробовать все методы, которые я упомянул выше, и некоторые, которые опубликовал @atlaste, и рассказать нам, какие из них, по вашему мнению, были самыми простыми / быстрыми / грязными / наиболее приемлемыми для запуска в среде PROD / и т.д.

В любом случае, удачи вам в выслеживании этого SO.

Если ваше приложение зависит от стороннего кода 3d (в Xsl-скриптах), то сначала вам нужно решить, хотите ли вы защищаться от ошибок в них или нет.Если вы действительно хотите защищаться, то я думаю, вам следует выполнить свою логику, которая подвержена внешним ошибкам, в отдельных доменах приложений.Ловить исключение StackOverflowException нехорошо.

Проверьте также это вопрос.

Сегодня у меня был 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
}

У меня это сработало, надеюсь, кому-то это поможет.

С .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");

        }

    }      
}

@WilliamJockusch, если я правильно понял вашу озабоченность, невозможно (с математической точки зрения) всегда определите бесконечную рекурсию, поскольку это означало бы решить Проблема остановки.Чтобы решить эту проблему, вам понадобится Суперрекурсивный алгоритм (например Предикаты методом проб и ошибок например) или машина, которая может гиперкомпьютер (пример объясняется в следующий раздел - доступно в качестве предварительного просмотра эта книга).

С практической точки зрения, вы должны были бы знать:

  • Сколько стековой памяти у вас осталось на данный момент времени
  • Сколько стековой памяти потребуется вашему рекурсивному методу в данный момент времени для конкретного вывода.

Имейте в виду, что на современных компьютерах эти данные чрезвычайно изменчивы из-за многозадачности, и я не слышал о программном обеспечении, которое выполняет эту задачу.

Дайте мне знать, если что-то будет неясно.

Судя по всему, кроме запуска другого процесса, похоже, нет никакого способа справиться с 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.Exited и Process.StandardOutput и исправляйте ошибки самостоятельно, чтобы предоставить вашим пользователям лучший опыт.

Вы можете просматривать это свойство каждые несколько звонков, Environment.StackTrace , и если трассировка стека превысила определенное пороговое значение, которое вы задали, вы можете вернуть функцию.

Вам также следует попробовать заменить некоторые рекурсивные функции циклами.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top