Как предотвратить и / или обработать исключение StackOverflowException? - PullRequest
67 голосов
/ 16 октября 2008

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

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

Ответы [ 10 ]

59 голосов
/ 16 октября 2008

от Microsoft:

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

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

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

  • Напишите код, который проверяет xsl на бесконечную рекурсию и уведомляет пользователя перед применением преобразования (Ugh).
  • Загрузка кода XslTransform в отдельный процесс (хакки, но меньше работы).

Вы можете использовать класс 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");

Процесс 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);
        }
    }
}
22 голосов
/ 06 июня 2015

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

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


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

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

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

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

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

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

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

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

Для этого передайте размер стека (в нашем случае: небольшое значение) параметру Thread и посмотрите, что произойдет. Размер стека по умолчанию в .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, который имеет смысл.

Создание исключений перед вами 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 в отдельном потоке ... но это, вероятно, не принесет вам пользы. Переполнение стека может произойти fast даже до того, как вы получите переключение контекста. Это означает, что этот механизм совсем не надежен ... Я бы не рекомендовал использовать его . Хотя было весело строить, вот код: -)

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. Используйте API профилирования .NET для захвата всех вызовов методов и используйте его для определения переполнения стека. Например, вы можете реализовать проверки, что если вы встречаетесь с одним и тем же методом X раз в дереве вызовов, вы даете сигнал. Здесь есть проект здесь , который даст вам преимущество.
8 голосов
/ 22 января 2010

Я бы предложил создать обертку вокруг объекта 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 голосов
/ 06 июня 2015

Этот ответ для @ WilliamJockusch.

Мне интересно, есть ли общий способ выследить StackOverflowExceptions. Другими словами, предположим, у меня есть бесконечное рекурсия где-то в моем коде, но я понятия не имею, где. я бы хотел отследить это с помощью некоторых средств, которые проще, чем пошаговое выполнение кода повсюду, пока я не увижу, что это происходит. Мне все равно, как взломать это. Например, было бы здорово иметь модуль, который я мог бы активировать, возможно, даже из другого потока, который опрашивал стек глубины и жаловался, если он достиг уровня, который я считал "слишком высоким". За Например, я мог бы установить «слишком высоко» до 600 кадров, полагая, что если стек был слишком глубоким, это должно быть проблемой. Это что-то подобное возможный. Другим примером может быть регистрация каждого 1000-го вызова метода в моем коде к выводу отладки. Шансы получить это свидетельство перегрузки было бы довольно хорошим, и, вероятно, не слишком сильно взорвать выход. Ключ в том, что он не может включать выписывать чек везде, где происходит переполнение. Потому что весь Проблема в том, что я не знаю, где это. Предпочтительно решение не должно зависеть от того, как выглядит моя среда разработки; то есть, не следует предполагать, что я использую C # через определенный набор инструментов (например, VS).

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

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

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

Этот метод является самым быстрым способом выявления проблемы.

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

Con's : дампы памяти очень велики, и вы должны подключить AdPlus / обработать процесс.

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

Pro's : Это, вероятно, самый простой способ для вас реализовать код, который проверяет размер стека вызовов из любого метода без написания кода в каждом методе вашего приложения. Существует множество AOP Frameworks , которые позволяют вам перехватывать до и после вызовов.

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

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

Con's : это повлияет на производительность - зацепки встроены в IL для каждого метода, и вы не сможете «деактивировать» его.

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

3. Регистрация активности пользователя.

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

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

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

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

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

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

Con's : не подходит для службы Windows , и я уверен, что есть более подходящие инструменты для веб-приложений .

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

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


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

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

3 голосов
/ 09 июля 2013

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

Раньше у меня был почти бесконечный цикл, подобный этому:

    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
}

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

3 голосов
/ 01 сентября 2010

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

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

2 голосов
/ 25 августа 2010

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

        }

    }      
}
0 голосов
/ 12 июня 2015

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

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

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

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

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

0 голосов
/ 11 июня 2015

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

0 голосов
/ 27 декабря 2012

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...