Участок обработки исключений что это
Добавил пользователь Алексей Ф. Обновлено: 19.09.2024
Глава 10 Обработка исключений
К механизму обработки исключений в Java имеют отношение 5 ключевых слов: — try , catch , throw , throws и finally . Схема работы этого механизма следующая. Вы пытаетесь (try) выполнить блок кода, и если при этом возникает ошибка, система возбуждает (throw) исключение, которое в зависимости от его типа вы можете перехватить (catch) или передать умалчиваемому (finally) обработчику.
Ниже приведена общая форма блока обработки исключений.
// блок кода >
catch (ТипИсключения1 е)
// обработчик исключений типа ТипИсключения 1 >
catch (ТипИсключения2 е)
// обработчик исключений типа ТипИсключения2
throw(e) // повторное возбуждение исключения >
В языке Delphi вместо ключевого слова catch используется except.
Типы исключени й
В вершине иерархии исключений стоит класс Throwable. Каждый из типов исключений является подклассом класса Throwable. Два непосредственных наследника класса Throwable делят иерархию подклассов исключений на две различные ветви. Один из них — класс Ехception — используется для описания исключительных ситуации, которые должны перехватываться программным кодом пользователя. Другая ветвь дерева подклассов Throwable — класс Error, который предназначен для описания исключительных ситуаций, которые при обычных условиях не должны перехватываться в пользовательской программе.
Неперехваченные исключения
Объекты-исключения автоматически создаются исполняющей средой Java в результате возникновения определенных исключительных состояний. Например, очередная наша программа содержит выражение, при вычислении которого возникает деление на нуль.
class Exc 0
public static void main(string args[])
int d = 0;
int a = 42 / d;
> >
Вот вывод, полученный при запуске нашего примера.
java.lang.ArithmeticException: / by zero
at Exc0.main(Exc0.java:4)
О братите внимание на тот факт что типом возбужденного исключения был не Exception и не Throwable. Это подкласс класса Exception, а именно: ArithmeticException, поясняющий, какая ошибка возникла при выполнении программы. Вот другая версия того же класса, в которой возникает та же исключительная ситуация, но на этот раз не в программном коде метода main.
class Exc1
static void subroutine()
int d = 0;
int a = 10 / d;
public static void main(String args[])
Exc1.subroutine();
Вывод этой программы показывает, как обработчик исключений исполняющей системы Java выводит содержимое всего стека вызовов.
java.lang.ArithmeticException: / by zero
at Exc1.subroutine(Exc1.java:4)
at Exc1.main(Exc1.java:7)
try и catch
Для задания блока программного кода, который требуется защитить от исключений, используется ключевое слово try. Сразу же после try-блока помещается блок catch, задающий тип исключения которое вы хотите обрабатывать.
class Exc2
public static void main(String args[])
int d = 0;
int a = 42 / d;
catch (ArithmeticException e)
System.out.println("division by zero");
> >
Целью большинства хорошо сконструированных catch-разделов должна быть обработка возникшей исключительной ситуации и приведение переменных программы в некоторое разумное состояние — такое, чтобы программу можно было продолжить так, будто никакой ошибки и не было (в нашем примере выводится предупреждение – division by zero ) .
Несколько разделов catch
В некоторых случаях один и тот же блок программного кода может возбуждать исключения различных типов. Для того, чтобы обрабатывать подобные ситуации, Java позволяет использовать любое количество catch-разделов для try-блока. Наиболее специализированные классы исключений должны идти первыми, поскольку ни один подкласс не будет достигнут, если поставить его после суперкласса. Следующая программа перехватывает два различных типа исключений, причем за этими двумя специализированными обработчиками следует раздел catch общего назначения, перехватывающий все подклассы класса Throwable.
class MultiCatch
public static void main(String args[])
int a = args.length;
System.out.println("a Courier New"; mso-bidi-font-weight: bold; mso-bidi-font-style: italic'>
int b = 42 / a;
int c[] = < 1 >;
c[42] = 99;
catch (ArithmeticException e)
System.out.println("div by 0: " + e);
catch(ArrayIndexOutOfBoundsException e)
System.out.println("array index oob: " + e);
Этот пример, запущенный без параметров, вызывает возбуждение исключительной ситуации деления на нуль. Если же мы зададим в командной строке один или несколько параметров, тем самым установив а в значение больше нуля, наш пример переживет оператор деления, но в следующем операторе будет возбуждено исключение выхода индекса за границы массива ArrayIndexOutOf Bounds. Ниже приведены результаты работы этой программы, запущенной и тем и другим способом.
С:\> java MultiCatch
а = 0
div by 0: java.lang.ArithmeticException: / by zero
C:\> java MultiCatch 1
a = 1
array index oob: java.lang.ArrayIndexOutOfBoundsException: 42
Вложенные операторы try
Операторы try можно вкладывать друг в друга аналогично тому, как можно создавать вложенные области видимости переменных. Если у оператора try низкого уровня нет раздела catch, соответствующего возбужденному исключению, стек будет развернут на одну ступень выше, и в поисках подходящего обработчика будут проверены разделы catch внешнего оператора try. Вот пример, в котором два оператора try вложены друг в друга посредством вызова метода.
class MultiNest
static void procedure()
int c[] = < 1 >;
c[42] = 99;
catch(ArrayIndexOutOfBoundsException e)
System.out.println("array index oob: " + e);
public static void main(String args[])
int a = args.length();
System.out.println("a Courier New"; mso-bidi-font-weight: bold; mso-bidi-font-style: italic'>
int b = 42 / a;
procedure();
catch (ArithmeticException e)
System.out.println("div by 0: " + e);
throw ОбъектТипаThrowable;
При достижении этого оператора нормальное выполнение кода немедленно прекращается, так что следующий за ним оператор не выполняется. Ближайший окружающий блок try проверяется на наличие соответствующего возбужденному исключению обработчика catch. Если такой отыщется, управление передается ему. Если нет, проверяется следующий из вложенных операторов try, и так до тех пор пока либо не будет найден подходящий раздел catch, либо обработчик исключений исполняющей системы Java не остановит программу, выведя при этом состояние стека вызовов. Ниже приведен пример, в котором сначала создается объект-исключение, затем оператор throw возбуждает исключительную ситуацию, после чего то же исключение возбуждается повторно — на этот раз уже кодом перехватившего его в первый раз раздела catch.
class ThrowDemo
static void demoproc()
throw new NullPointerException("demo");
catch (NullPointerException e)
System.out.println("caught inside demoproc");
throw e;
public static void main(String args[])
demoproc();
catch(NulPointerException e)
System.out.println("recaught: " + e);
С:\> java ThrowDemo
caught inside demoproc
recaught: java.lang.NullPointerException: demo
Если метод способен возбуждать исключения, которые он сам не обрабатывает, он должен объявить о таком поведении, чтобы вызывающие методы могли защитить себя от этих исключений. Для задания списка исключений, которые могут возбуждаться методом, используется ключевое слово throws . Если метод в явном виде (т.е. с помощью оператора throw) возбуждает исключение соответствующего класса, тип класса исключений должен быть указан в операторе throws в объявлении этого метода. С учетом этого наш прежний синтаксис определения метода должен быть расширен следующим образом:
тип имя_метода(список аргументов) throws список_исключений <>
Ниже приведен пример программы, в которой метод procedure пытается возбудить исключение, не обеспечивая ни программного кода для его перехвата, ни объявления этого исключения в заголовке метода. Такой программный код не будет оттранслирован.
class ThrowsDemo1
static void procedure()
System.out.println("inside procedure");
throw new IllegalAccessException("demo");
public static void main(String args[])
procedure();
Для того, чтобы мы смогли оттранслировать этот пример, нам придется сообщить транслятору, что procedure может возбуждать исключения типа IllegalAccessException и в методе main добавить код для обработки этого типа исключений :
class ThrowsDemo
static void procedure() throws IllegalAccessException
System.out.println(" inside procedure");
throw new IllegalAccessException("demo");
public static void main(String args[])
procedure();
catch (IllegalAccessException e)
System.out.println("caught " + e);
Ниже приведен результат выполнения этой программы.
С:\> java ThrowsDemo
inside procedure
caught java.lang.IllegalAccessException: demo
Иногда требуется гарантировать, что определенный участок кода будет выполняться независимо от того, какие исключения были возбуждены и перехвачены. Для создания такого участка кода используется ключевое слово finally. Даже в тех случаях, когда в методе нет соответствующего возбужденному исключению раздела catch, блок finally будет выполнен до того, как управление перейдет к операторам, следующим за разделом try. У каждого раздела try должен быть по крайней мере или один раздел catch или блок finally. Блок finally очень удоб е н для закрытия файлов и освобождения любых других ресурсов, захваченных для временного использования в начале выполнения метода. Ниже приведен пример класса с двумя методами, завершение которых происходит по разным причинам, но в обоих перед выходом выполняется код раздела finally.
class FinallyDemo
static void procA()
System.out.println("inside procA");
throw new RuntimeException("demo");
System.out.println("procA's finally");
static void procB()
System.out.println("inside procB");
return;
System.out.println("procB's finally");
public static void main(String args[])
procA();
catch (Exception e) <>
procB();
С:\> java FinallyDemo
inside procA
procA's finally
inside procB
procB's finally
Подклассы Exception
Только подклассы класса Throwable могут быть возбуждены или перехвачены. Простые типы — int, char и т.п., а также классы, не являющиеся подклассами Throwable, например, String и Object, использоваться в качестве исключений не могут. Наиболее общий путь для использования исключений — создание своих собственных подклассов класса Exception. Ниже приведена программа, в которой объявлен новый подкласс класса Exception.
class MyException extends Exception
private int detail;
MyException(int a)
detail = a:
public String toString()
return "MyException[" + detail + "]";
class ExceptionDemo
static void compute(int a) throws MyException
System.out.println("called computer + a + ").");
if (a > 10)
throw new MyException(a);
System.out.println("normal exit.");
public static void main(String args[])
compute(1);
compute(20);
catch (MyException e)
System.out.println("caught" + e);
Этот пример довольно сложен. В нем сделано объявление подкласса MyException класса Exception. У этого подкласса есть специальный конструктор, который записывает в переменную объекта целочисленное значение, и совмещенный метод toString, выводящий значение, хранящееся в объекте-исключении. Класс ExceptionDemo определяет метод compute, который возбуждает исключение типа MyExcepton. Простая логика метода compute возбуждает исключение в том случае, когда значение пара-ветра метода больше 10. Метод main в защищенном блоке вызывает метод compute сначала с допустимым значением, а затем — с недопустимым (больше 10), что позволяет продемонстрировать работу при обоих путях выполнения кода. Ниже приведен результат выполнения программы.
С:\> java ExceptionDemo
called compute(1).
normal exit.
called compute(20).
caught MyException[20]
Заключительное резюме
Обработка исключений предоставляет исключительно мощный механизм для управления сложными программами. Try, throw, catch дают вам простой и ясный путь для встраивания обработки ошибок и прочих нештатных ситуаций в программную логику. Если вы научитесь должным образом использовать рассмотренные в данной главе механизмы, это придаст вашим классам профессиональный вид, и любые будущие пользователи вашего программного кода, несомненно, оценят это.
Эта статья посвящена обработке исключений. В качестве примеров используются языковые конструкции Visual C++ 6.5.
1. Фреймовая обработка исключений
1.1. Исключения и их обработчики
Исключение – это событие, которое произошло во время выполнения программы, в результате совершения которого дальнейшее нормальное выполнение программы становится невозможным. Обычно такие события происходят из-за ошибок в программе или неправильных действий пользователя (впрочем, хороший программист не может рассчитывать на то, что пользователь всегда будет действовать правильно). После возникновения исключения требуется привести программу в рабочее состояние или выполнить её аварийное завершение с освобождением всех ресурсов, которые использовались программой.
Для выполнения описанных выше действий в операционных системах Windows предназначен механизм структурной обработки исключений (structured exception handling, SHE). Работает это так. В программе выделяется блок программного кода, где может произойти исключение. Этот блок кода называется фреймом, а сам код называется охраняемым кодом. После фрейма вставляется программный блок, где обрабатывается исключение. Этот блок называется обработчиком исключения. Когда исключение будет обработано, управление передается первой инструкции, которая следует за обработчиком исключения.
- EXCEPTION_EXECUTE_HANDLER – управление передается обработчику исключений;
- EXCEPTION_CONTINUE_SEARCH – система продолжает поиск обработчика исключения;
- EXCEPTION_CONTINUE_EXECUTION – система передает управление в точку прерывания программы.
Переменные, объявленные внутри фрейма или блока обработки исключения, являются локальными и видны только внутри соответствующего блока, как это принято в С++. Пример обработки исключения:
1.2. Как получить код исключения
- EXCEPTION_ACCESS_VIOLATION – попытка чтения или записи в виртуальную память без соответствующих прав доступа;
- EXCEPTION_BREAKPOINT – встретилась точка останова;
- EXCEPTION_DATATYPE_MISALIGNMENT – доступ к данным, адрес которых не выровнен по границе слова или двойного слова;
- EXCEPTION_SINGLE_STEP – механизм трассировки программы сообщает, что выполнена одна инструкция;
- EXCEPTION_ARRAY_BIUNDS_EXCEEDED – выход за пределы массива, если аппаратное обеспечение поддерживает такую проверку;
- EXCEPTION_FLT_DENORMAL_OPERAND – один из операндов с плавающей точкой является ненормализованным;
- EXCEPTION_FLT_DIVIDE_BY_ZERO – попытка деления на ноль в операции с плавающей точкой;
- EXCEPTION_FLT_INEXACT_RESULT – результат операции с плавающей точкой не может быть точно представлен десятичной дробью;
- EXCEPTION_FLT_INVALID_OPERATION – ошибка в операции с плавающей точкой, для которой не предусмотрены другие коды исключения;
- EXCEPTION_FLT_OVERFLOW – при выполнении операции с плавающей точкой произошло переполнение;
- EXCEPTION_FLT_STACK_CHECK – переполнение или выход за нижнюю границу стека при выполнении операции с плавающей точкой;
- EXCEPTION_FLT_UNDERFLOW – результат операции с плавающей точкой является числом, которое меньше минимально возможного числа с плавающей точкой;
- EXCEPTION_INT_DIVIDE_BY_ZERO – попытка деления на ноль при операции с целыми числами;
- EXCEPTION_INT_OVERFLOW – при выполнении операции с целыми числами произошло переполнение;
- EXCEPTION_PRIV_INSTRUCTION – попытка выполнения привилегированной инструкции процессора, которая недопустима в текущем режиме процессора;
- EXCEPTION_NONCONTINUABLE_EXCEPTION – попытка возобновления исполнения программы после исключения, которое запрещает выполнять такое действие.
1.3. Функции фильтра
Если есть необходимость более детально обработать информацию об исключении, то в выражении-фильтре используют функцию, которая в этом случае называется функцией фильтра. В функции фильтра нельзя вызывать функции GetExceptionCode и GetExceptionInformation. Однако эти функции могут вызываться для инициализации параметров функции фильтра.
Пример программы, в которой используется функция фильтра для принятия решения о дальнейшей обработке исключения, приведён ниже. Здесь функция фильтра (ff) возвращает одно из двух значений EXCEPTION_CONTINUE_EXECUTION или EXCEPTION_EXECUTE_HANDLER. Первое значение возвращается в том случае, если исключение генерируется системой при целочисленном делении на ноль, а второе – в остальных случаях. При попытке деления на ноль происходит исключение и в качестве выражения-фильтра применяется результат выполнения функции ff. Эта функция проверяет, чем было вызвано исключение, и если это деление на ноль, то ошибка исправляется (а = 10). Затем функция возвращает значение EXCEPTION_CONTINUE_EXECUTION, то есть программа продолжает свою работу, но уже с исправленным значением переменной a. Если же это исправление не сделать, то программа войдет в бесконечный цикл.
1.4. Необработанные исключения
- EXCEPTION_CONTINUE_SEARCH – передать управление отладчику приложения;
- EXCEPTION_EXECUTE_HANDLER – передать управление обработчику исключений.
- EXCEPTION_EXECUTE_HANDLER – выполнение программы прекращается;
- EXCEPTION_CONTINUE_EXECUTION – возобновить исполнение программы с точки исключения;
- EXCEPTION_CONTINUE_SEARCH – выполняется системная функция UnhandledExceptionFilter.
1.5. Обработка исключений при операциях с плавающей точкой
По умолчанию система отключает все исключения с плавающей точкой. Поэтому если при выполнении операции с плавающей точкой было получено число, которое не входит в диапазон представления чисел с плавающей точкой, то в результате система вернет NAN или INFINITY в случае слишком малого или слишком большого числа соответственно. Чтобы включить режим генерации исключений с плавающей точкой нужно изменить состояние слова, управляющего обработкой операций с плавающей точкой. Это можно сделать при помощи функции _controlfp, которая имеет следующий прототип: Прототип определен в заголовочном файле float.h. Эта функция возвращает старое слово, управляющее обработкой исключений. Параметр new задает новое управляющее слово, а параметр mask должен принимать значение _MCW_EM. Если значение этого параметра равно 0, то функция возвращает старое управляющее слово.
- _EM_INVALID – исключение EXCEPTION_FLT_INVALID_OPERATION;
- _EM_DENORMAL – исключение EXCEPTION_FLT_DENORMAL_OPERAND;
- _EM_ZERODIVIDE – исключение EXCEPTION_FLT_DIVIDE_BY_ZERO;
- _EM_OVERFLOW – исключение EXCEPTION_FLT_OVERFLOW;
- _EM_UNDERFLOW – исключение EXCEPTION_FLT_UNDERFLOW;
- _EM_INEXACT – исключение EXCEPTION_FLT_INEXACT_RESULT.
1.6. Использование блоков try и catch
2. Финальная обработка исключений
2.1. Финальные блоки фрейма
В операционных системах Windows существует еще один способ обработки исключений. При этом способе код, где возможно возникновение исключения, также заключается в блок __try. Но теперь за этим блоком следует блок __finally. В таком случае блок __finally выполняется всегда – независимо от того, произошло исключение или нет. Такой способ обработки исключений называется финальная обработка исключений. Структурно финальная обработка выглядит следующим образом: Финальная обработка исключений используется для того, чтобы при любом исходе исполнения блока __try освободить ресурсы (память, файлы и т.п.), которые были захвачены внутри этого блока.
Недостатком такого метода является то, что финальный код будет выполняться в любом случае. А это не всегда хорошо. Например, если мы пытаемся освободить память, которая распределяется в блоке __try, то это может привести к ошибке, если до распределения памяти дело не дошло (исключение произошло раньше). Чтобы избежать такой ситуации, нужно проверить, как завершился блок __try – нормально или нет.
2.2. Проверка завершения фрейма
- Нормальное завершение блока.
- Выход из блока при помощи управляющей инструкции __leave.
- Выход из блока при помощи одной из управляющих инструкций return, break, continue или goto.
- Передача управления обработчику исключения.
Чтобы определить, как завершился блок __try, используется функция AbnormalTermination, которая имеет следующий прототип: В случае если блок __try завершился ненормально, эта функция возвращает ненулевое значение, иначе – значение FALSE. Используя эту функцию, ресурсы, захваченные в блоке __try, можно освобождать в зависимости от ситуации. Пример:
1. Дайте определение понятию “исключение”
2. Какова иерархия исключений.
3. Можно/нужно ли обрабатывать ошибки jvm?
4. Какие существуют способы обработки исключений?
5. О чем говорит ключевое слово throws?
6. В чем особенность блока finally? Всегда ли он исполняется?
7. Может ли не быть ни одного блока catch при отлавливании исключений?
8. Могли бы вы придумать ситуацию, когда блок finally не будет выполнен?
9. Может ли один блок catch отлавливать несколько исключений (с одной и разных веток наследований)?
10. Что вы знаете об обрабатываемых и не обрабатываемых (checked/unchecked) исключениях?
11. В чем особенность RuntimeException?
12. Как написать собственное (“пользовательское”) исключение? Какими мотивами вы будете руководствоваться при выборе типа исключения: checked/unchecked?
13. Какой оператор позволяет принудительно выбросить исключение?
14. Есть ли дополнительные условия к методу, который потенциально может выбросить исключение?
15. Может ли метод main выбросить исключение во вне и если да, то где будет происходить обработка данного исключения?
16. Если оператор return содержится и в блоке catch и в finally, какой из них “главнее”?
17. Что вы знаете о OutOfMemoryError?
18. Что вы знаете о SQLException? К какому типу checked или unchecked оно относится, почему?
19. Что такое Error? В каком случае используется Error. Приведите пример Error’а.
20. Какая конструкция используется в Java для обработки исключений?
21. Предположим, есть блок try-finally. В блоке try возникло исключение и выполнение переместилось в блок finally. В блоке finally тоже возникло исключение. Какое из двух исключений “выпадет” из блока try-finally? Что случится со вторым исключением?
22. Предположим, есть метод, который может выбросить IOException и FileNotFoundException в какой последовательности должны идти блоки catch? Сколько блоков catch будет выполнено?
Ответы
1. Дайте определение понятию “исключение”
Исключение – это проблема(ошибка) возникающая во время выполнения программы. Исключения могут возникать во многих случаях, например:
- Пользователь ввел некорректные данные.
- Файл, к которому обращается программа, не найден.
- Сетевое соединение с сервером было утеряно во время передачи данных. И т.д.
Все исключения в Java являются объектами. Поэтому они могут порождаться не только автоматически при возникновении исключительной ситуации, но и создаваться самим разработчиком.
2. Какова иерархия исключений.
Исключения делятся на несколько классов, но все они имеют общего предка — класс Throwable. Его потомками являются подклассы Exception и Error.
Исключения (Exceptions) являются результатом проблем в программе, которые в принципе решаемые и предсказуемые. Например, произошло деление на ноль в целых числах.
Ошибки (Errors) представляют собой более серьёзные проблемы, которые, согласно спецификации Java, не следует пытаться обрабатывать в собственной программе, поскольку они связаны с проблемами уровня JVM. Например, исключения такого рода возникают, если закончилась память, доступная виртуальной машине. Программа дополнительную память всё равно не сможет обеспечить для JVM.
В Java все исключения делятся на два типа: контролируемые исключения (checked) и неконтролируемые исключения (unchecked), к которым относятся ошибки (Errors) и исключения времени выполнения (RuntimeExceptions, потомок класса Exception).
Контролируемые исключения представляют собой ошибки, которые можно и нужно обрабатывать в программе, к этому типу относятся все потомки класса Exception (но не RuntimeException).
3. Можно/нужно ли обрабатывать ошибки jvm?
Обрабатывать можно, но делать этого не стоит. Разработчику не предоставлены инструменты для обработки ошибок системы и виртуальной машины.
4. Какие существуют способы обработки исключений?
- try – данное ключевое слово используется для отметки начала блока кода, который потенциально может привести к ошибке.
- catch – ключевое слово для отметки начала блока кода, предназначенного для перехвата и обработки исключений.
- finally – ключевое слово для отметки начала блока кода, которой является дополнительным. Этот блок помещается после последнего блока ‘catch’. Управление обычно передаётся в блок ‘finally’ в любом случае.
- throw – служит для генерации исключений.
- throws – ключевое слово, которое прописывается в сигнатуре метода, и обозначающее что метод потенциально может выбросить исключение с указанным типом.
Общий вид конструкции для “поимки” исключительной ситуации выглядит следующим образом:
В прошлом уроке мы использовали многопоточность для создания анимированных (или даже интерактивных) экранов загрузки, а также для уменьшения времени загрузки в целом.
И хотя мы рассмотрели множество вещей на примере Roche Fusion, кое-что мы не затронули вовсе — что, если что-то пойдет не так? Или, более техническим языком — что, если один из наших потоков вернет исключение?
Если мы ничего не будем с этим делать, то поток, который вернет исключение (например, из-за бага или испорченного файла), завершится без нашего ведома, и мы никогда об этом не узнаем.
Так происходит, потому что исключения постепенно накапливаются в стеке вызовов до тех пор, пока не будут обработаны или не заполнят весь стек. В последнем случае поток, в котором появилось исключение, завершает работу.
Если проблема происходит в главном потоке, то пользователь просто увидит ошибку, но если речь идет о других потоках, то они при исключении завершаются без единого следа, и программа может повиснуть, ожидая успешного завершения работы аварийно остановившегося потока.
Заметьте, что ниже описанные решения этой проблемы применимы к любым многопоточным приложениям. Загрузочные экраны служат лишь в качестве примера.
Возможные решения
Существует несколько возможных способов обрабатывать исключения в потоках.
Например, главный поток может регулярно проверять, остановились ли другие и сделали ли они что-нибудь с возникшим исключением.
Проблема этого решения в том, что нам нужно быть в курсе всех выполняемых потоков, в ином случае какой-нибудь поток все равно экстренно завершится, не подав вида.
Альтернативно мы можем подписаться на глобальные события, происходящие во всем приложении, которые будут информировать нас о произошедших исключениях, так, что мы сможем реагировать на них.
Используем то, что у нас уже есть
Ранее мы расписали архитектуру небольшого фреймворка, и я бы хотел представить вам решение, которое не приводит к вышеописанным проблемам.
В приложении, написанном нами в прошлом уроке, любой поток мог давать главному потоку работу на выполнение. Каждый поток может сам обрабатывать исключения таким образом, что все возникшие ошибки передавались бы главному потоку. В таком случае исключение либо заставит упасть все приложение, либо будет обработано должным образом. Вот простой пример реализации такой идеи:
actionQueue — это объект класса, разработанного нами в этом посте, который позволяет перенаправлять из всех потоков какие-либо действия на главный поток.
Таким образом, когда threadAction возвращает исключение, оно не обрабатывается, а помещается в очередь на исполнение в главном потоке.
Когда главный поток доберется до исключения в очереди действий, он уже обработает его как надо, а если не сможет, то приложение просто закончит работу.
Данный подход отлично работает на первый взгляд, но есть одна проблема, которая очень быстро становится явной при отладке — при возвращении исключения, которое уже возвращалось ранее, его стек вызовов автоматически перезаписывается. И без начального стека вызовов мы даже не сможем определить, в какой части программы возникло исключение.
Сохраняем стеки вызовов
К счастью, у этой проблемы есть очень легкое решение.
Внесем поправку в написанный выше код:
И это все, что нам нужно, чтобы убедиться в том, что мы не теряем информацию о предыдущем исключении при возвращении такого же нового.
Дополнительно
Конечно, если возможно, то лучше проектировать ваше приложение так, что возможность возникновения фатального исключения стремится к нулю.
Например, мы в Roche Fusion оборачиваем каждый скриптовый файл в try-catch . В случае, если что-то пойдет не так, в игровую консоль выводится предупреждение и загрузка продолжается.
В большинстве случаев это никак не влияет на игру, разве что некоторый контент может отображаться не так, как надо.
Однажды мы все-таки не обратили внимание на одно из предупреждений и напоролись на ситуацию, в которой при определенных обстоятельствах приложение могло крашиться из-за того, что один скрипт не загружался. Но в основном такой подход намного облегчает разработку и моддинг.
Заключение
Надеюсь, что эта статья дала вам представление о том, как надо обрабатывать возникающие исключения в многопоточных приложениях.
Если вас заинтересовали другие подходы, которые я упомянул, но о которых не рассказал подробнее, рекомендую поискать их онлайн.
Читайте также: