Участок обработки исключений что это

Добавил пользователь Алексей Ф.
Обновлено: 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, использоваться в качестве исключений не могут. Наиболее общий путь для использова­ния исключений — создание своих собственных подклассов класса Ex­ception. Ниже приведена программа, в которой объявлен новый подкласс класса 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. Дайте определение понятию “исключение”

Исключение – это проблема(ошибка) возникающая во время выполнения программы. Исключения могут возникать во многих случаях, например:

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

Все исключения в Java являются объектами. Поэтому они могут порождаться не только автоматически при возникновении исключительной ситуации, но и создаваться самим разработчиком.

2. Какова иерархия исключений.

Собеседование по Java – исключения (exceptions) (вопросы и ответы)

Исключения делятся на несколько классов, но все они имеют общего предка — класс Throwable. Его потомками являются подклассы Exception и Error.

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

Ошибки (Errors) представляют собой более серьёзные проблемы, которые, согласно спецификации Java, не следует пытаться обрабатывать в собственной программе, поскольку они связаны с проблемами уровня JVM. Например, исключения такого рода возникают, если закончилась память, доступная виртуальной машине. Программа дополнительную память всё равно не сможет обеспечить для JVM.

В Java все исключения делятся на два типа: контролируемые исключения (checked) и неконтролируемые исключения (unchecked), к которым относятся ошибки (Errors) и исключения времени выполнения (RuntimeExceptions, потомок класса Exception).

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

3. Можно/нужно ли обрабатывать ошибки jvm?

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

4. Какие существуют способы обработки исключений?

  1. try – данное ключевое слово используется для отметки начала блока кода, который потенциально может привести к ошибке.
  2. catch – ключевое слово для отметки начала блока кода, предназначенного для перехвата и обработки исключений.
  3. finally – ключевое слово для отметки начала блока кода, которой является дополнительным. Этот блок помещается после последнего блока ‘catch’. Управление обычно передаётся в блок ‘finally’ в любом случае.
  4. throw – служит для генерации исключений.
  5. throws – ключевое слово, которое прописывается в сигнатуре метода, и обозначающее что метод потенциально может выбросить исключение с указанным типом.

Общий вид конструкции для “поимки” исключительной ситуации выглядит следующим образом:

Обложка: Обработка исключений в многопоточных приложениях

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

И хотя мы рассмотрели множество вещей на примере Roche Fusion, кое-что мы не затронули вовсе — что, если что-то пойдет не так? Или, более техническим языком — что, если один из наших потоков вернет исключение?

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

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

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

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

Возможные решения

Существует несколько возможных способов обрабатывать исключения в потоках.

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

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

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

Используем то, что у нас уже есть

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

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

actionQueue — это объект класса, разработанного нами в этом посте, который позволяет перенаправлять из всех потоков какие-либо действия на главный поток.

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

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

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

Сохраняем стеки вызовов

К счастью, у этой проблемы есть очень легкое решение.

Внесем поправку в написанный выше код:

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

Дополнительно

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

Например, мы в Roche Fusion оборачиваем каждый скриптовый файл в try-catch . В случае, если что-то пойдет не так, в игровую консоль выводится предупреждение и загрузка продолжается.

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

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

Заключение

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

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

Читайте также: