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

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

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

Ответ 1

От Microsoft:

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

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

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

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

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

EDIT: я только что протестировал, вот как это сделать:

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

Ответ 2

ПРИМЕЧАНИЕ Вопрос в щебет @WilliamJockusch и исходный вопрос различны.

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


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

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

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

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

Обнаружение в тестовой среде

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

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

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

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

Другие подходы

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

  • Обработка путем размещения процесса CLR и его обработки. Обратите внимание, что вы все еще не можете "поймать" его.
  • Изменение кода IL, создание другой DLL, добавление проверок на рекурсию. Да, это вполне возможно (я реализовал это в прошлом:-); это просто сложно и включает в себя много кода, чтобы все было правильно.
  • Используйте API профилирования .NET для захвата всех вызовов методов и используйте их для определения. Например, вы можете реализовать проверки, что если вы столкнулись с одним и тем же методом X раз в дереве вызовов, вы даете сигнал. Там есть проект здесь, который даст вам начало.

Ответ 3

Я бы предложил создать обертку вокруг объекта XmlWriter, поэтому он будет подсчитывать количество вызовов WriteStartElement/WriteEndElement, и если вы ограничите количество тегов некоторым числом (fe 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

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

Также проверьте question.

Ответ 5

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

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

Ответ 6

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

        }

    }      
}

Ответ 7

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

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

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

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

Pro: Дампы памяти - это верный способ, позволяющий решить проблему. С# MVP и я вместе работали над поиском и устранением неисправностей, и он продолжал вести блог об этом здесь.

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

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

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

2. Ориентированное по ориентации программирование.

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

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

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

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

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

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

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

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

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

Можно подсчитать, сколько методов событий подписано слишком просто, чем при использовании АОП.

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

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

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

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

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

 


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

В любом случае удачи удалили это SO.

Ответ 8

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

Ответ 9

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

С практической точки зрения вам нужно знать:

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

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

Сообщите мне, если что-то неясно.

Ответ 10

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

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