C # перехватывает исключение переполнения стека

116

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

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

Не уверен, если актуально, но дополнительная информация:

  • исключение не создается в основном потоке

  • объект, в котором код генерирует исключение, загружается вручную с помощью Assembly.LoadFrom (...). CreateInstance (...)

Toto
источник
3
@RichardOD, конечно, я исправил ошибку, потому что это была ошибка. Однако проблема может проявляться и по-другому, и я хочу с ней справиться
Тото
7
Согласовано, переполнение стека является серьезной ошибкой , которая не может быть перехвачено , потому что это не должно быть пойманным. Вместо этого исправьте неработающий код.
Ян Кемп,
11
@RichardOD: Если кто-то хочет спроектировать, например, синтаксический анализатор с рекурсивным спуском, а не налагать искусственные ограничения на глубину, помимо тех, которые фактически требуются хост-машине, как это сделать? Если бы у меня были мои druthers, было бы исключение StackCritical, которое можно было бы явно перехватить, и оно было бы запущено, пока еще оставалось немного места в стеке; он отключался, пока не был фактически брошен, и не мог быть пойман, пока не оставалось безопасное количество места в стеке.
supercat
3
Этот вопрос полезен - я хочу провалить модульный тест, если возникает исключение переполнения стека - но NUnit просто перемещает тест в категорию «игнорируемых» вместо того, чтобы терпеть неудачу, как это было бы с другими исключениями - мне нужно его поймать и сделайте Assert.Failвместо этого. Итак, серьезно - как мы это делаем?
BrainSlugs83

Ответы:

110

Начиная с версии 2.0 исключение StackOverflow может быть обнаружено только в следующих случаях.

  1. CLR запускается в размещенной среде *, где хост специально позволяет обрабатывать исключения StackOverflow
  2. Исключение stackoverflow вызывается пользовательским кодом, а не реальной ситуацией переполнения стека ( ссылка )

* «размещенная среда», например «мой код размещает CLR, и я настраиваю параметры CLR», а не «мой код работает на общем хостинге»

JaredPar
источник
29
Если его нельзя поймать ни в одном соответствующем Scebario, почему существует объект StackoverflowException?
Ману
9
@Manu по крайней мере по паре причин. 1) Это что-то вроде того, что могло быть поймано в 1.1 и, следовательно, имело цель. 2) Его все еще можно поймать, если вы размещаете CLR, поэтому это все еще допустимый тип исключения
JaredPar
3
Если это невозможно поймать ... Почему событие Windows, объясняющее, что произошло, по умолчанию не включает полную трассировку стека?
11
Как разрешить обработку StackOverflowExceptions в размещенной среде? Причина, по которой я спрашиваю, заключается в том, что я запускаю размещенную среду, и у меня именно такая проблема, когда она уничтожает весь пул приложений. Я бы предпочел, чтобы он прервал поток, где он может вернуться к началу, а затем я могу зарегистрировать ошибку и продолжить, не убивая все потоки пула приложений.
Brain2000
Starting with 2.0 ...Мне любопытно, что им мешает поймать ТАК и как это было возможно 1.1(вы упомянули об этом в своем комментарии)?
М.казем Ахгары
47

Правильный способ - исправить перелив, но ....

Вы можете увеличить стек: -

using System.Threading;
Thread T = new Thread(threadDelegate, stackSizeInBytes);
T.Start();

Вы можете использовать свойство System.Diagnostics.StackTrace FrameCount, чтобы подсчитать использованные кадры и выдать собственное исключение при достижении предела кадров.

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

class Program
{
    static int n;
    static int topOfStack;
    const int stackSize = 1000000; // Default?

    // The func is 76 bytes, but we need space to unwind the exception.
    const int spaceRequired = 18*1024; 

    unsafe static void Main(string[] args)
    {
        int var;
        topOfStack = (int)&var;

        n=0;
        recurse();
    }

    unsafe static void recurse()
    {
        int remaining;
        remaining = stackSize - (topOfStack - (int)&remaining);
        if (remaining < spaceRequired)
            throw new Exception("Cheese");
        n++;
        recurse();
    }
}

Просто поймай Сыр. ;)


источник
48
Cheeseдалеко не конкретный. Я бы пошел наthrow new CheeseException("Gouda");
C.Evenhuis
14
@ C.Evenhuis. Хотя нет никаких сомнений в том, что Гауда - исключительный сыр, это должно быть исключение RollingCheeseException («Двойной Глостер»), на самом деле, см. Cheese-rolling.co.uk
3
lol, 1) исправление невозможно, потому что, не уловив его, вы часто не знаете, где это происходит 2) увеличение размера стека бесполезно с бесконечной рекурсией и м 3) проверка стека в правильном месте похожа на первую
Фиро,
2
но у меня непереносимость лактозы
redoc
39

На странице MSDN в StackOverflowException s:

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

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

Damien_The_Unbeliever
источник
23

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

Для этого вам нужно открыть настройки исключений из меню «Отладка». В более старых версиях Visual Studio это «Отладка» - «Исключения»; в более новых версиях это «Отладка» - «Windows» - «Настройки исключений».

После открытия настроек разверните «Исключения среды CLR», разверните «Система», прокрутите вниз и отметьте «System.StackOverflowException». Затем вы можете посмотреть стек вызовов и найти повторяющийся образец вызовов. Это должно дать вам представление о том, где искать, чтобы исправить код, вызывающий переполнение стека.

Саймон
источник
1
Где находится отладка - исключения в VS 2015?
FrenkyB
1
Отладка - Windows - Настройки исключений
Саймон,
15

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

http://msdn.microsoft.com/en-us/library/system.appdomain.unhandledexception.aspx

Начиная с .NET Framework версии 4, это событие не возникает для исключений, которые нарушают состояние процесса, таких как переполнение стека или нарушения доступа, если только обработчик событий не является критичным для безопасности и не имеет атрибута HandleProcessCorruptedStateExceptionsAttribute.

Тем не менее, ваше приложение будет завершено после выхода из функции события (ОЧЕНЬ грязный обходной путь заключался в перезапуске приложения в этом событии, ха-ха, этого не было и никогда не будет). Но для регистрации вполне достаточно!

В .NET Framework версий 1.0 и 1.1 необработанное исключение, которое возникает в потоке, отличном от основного потока приложения, перехватывается средой выполнения и поэтому не приводит к завершению работы приложения. Таким образом, событие UnhandledException может возникнуть без завершения работы приложения. Начиная с .NET Framework версии 2.0, эта задержка для необработанных исключений в дочерних потоках была удалена, поскольку совокупный эффект таких тихих сбоев включал снижение производительности, поврежденные данные и блокировки, которые было трудно отлаживать. Дополнительные сведения, включая список случаев, в которых среда выполнения не завершается, см. В разделе Исключения в управляемых потоках.

FooBarTheLittle
источник
6

Да из CLR 2.0 переполнение стека считается неустранимой ситуацией. Таким образом, среда выполнения по-прежнему завершает процесс.

Подробнее см. В документации http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx

Брайан Расмуссен
источник
Начиная с CLR 2.0, StackOverflowExceptionпо умолчанию завершает процесс.
Брайан Расмуссен,
Нет. Вы можете поймать OOM, и в некоторых случаях это может иметь смысл. Я не знаю, что вы имеете в виду под исчезновением нити. Если поток имеет необработанное исключение, среда CLR завершит процесс. Если ваш поток завершит свой метод, он будет очищен.
Брайан Расмуссен,
5

Вы не можете. CLR вам не позволит. Переполнение стека является фатальной ошибкой и не может быть исправлено.

Мэтью Шарли
источник
Итак, как сделать так, чтобы модульный тест завершился неудачно для этого исключения, если вместо того, чтобы его уловить, он приводит к сбою средства выполнения модульного теста?
BrainSlugs83
1
@ BrainSlugs83. Вы этого не сделаете, потому что это глупая идея. Почему вы все равно тестируете, если ваш код не работает с исключением StackOverflowException? Что произойдет, если среда CLR изменится и сможет обрабатывать более глубокий стек? Что произойдет, если вы вызовете свою модульно протестированную функцию где-нибудь, где уже есть глубоко вложенный стек? Это похоже на то, что нельзя протестировать. Если вы пытаетесь бросить вручную, выберите более подходящее исключение для задачи.
Мэтью Шарли
5

Вы не можете, поскольку большинство сообщений объясняют, позвольте мне добавить еще одну область:

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

Нет проблемы сена
источник
3

Это невозможно, и по уважительной причине (например, подумайте обо всех этих уловках (Exception) {} вокруг).

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

ИМА
источник
2
Операторы «catch» на самом деле не были бы проблемой, так как к тому времени, когда оператор catch мог бы выполняться, система откатила бы результаты всех попыток использовать два больших пространства стека. Нет причин, по которым перехват исключений переполнения стека должен быть опасным. Причина, по которой такие исключения не могут быть обнаружены, заключается в том, что для их безопасного обнаружения потребовалось бы добавить некоторые дополнительные накладные расходы ко всему коду, который использует стек, даже если он не переполняется.
supercat 05
4
В какой-то момент утверждение не очень хорошо продумано. Если вы не можете поймать Stackoverflow, возможно, вы никогда не узнаете, ГДЕ это произошло в производственной среде.
Offler