Control.Exception
| Copyright | (c) The University of Glasgow 2001 |
|---|---|
| License | BSD-style (see the file libraries/base/LICENSE) |
| Maintainer | libraries@haskell.org |
| Stability | experimental |
| Portability | non-portable (extended exceptions) |
| Safe Haskell | Trustworthy |
| Language | Haskell2010 |
Содержание
Описание
Этот модуль обеспечивает поддержку генерации и перехвата как встроенных, так и определяемых пользователем исключений.
В дополнение к исключениям, выбрасываемым операциями IO, исключения могут быть вызваны чистым кодом (неточными исключениями) или внешними событиями (асинхронными исключениями), но могут быть перехвачены только в монаде IO. Для получения более подробной информации см.:
- Семантика неточных исключений, Саймон Пейтон Джонс, Алистэр Рид, Тони Хоар, Саймон Марлоу, Фергус Хендерсон, в PLDI'99.
- Асинхронные исключения в Haskell, Саймон Марлоу, Саймон Пейтон Джонс, Энди Моран и Джон Реппи, в PLDI'01.
- Расширяемая динамически типизированная иерархия исключений, Саймон Марлоу, в Haskell '06.
Тип Exception
data SomeException Source
Тип SomeException является корнем иерархии типов исключений. Когда выбрасывается исключение типа e, за кулисами оно инкапсулируется в SomeException.
Конструкторы
| forall e . Exception e => SomeException e |
Экземпляры
class (Typeable e, Show e) => Exception e where Source
Любой тип, который вы хотите выбросить или перехватить как исключение, должен быть экземпляром класса Exception. Простейший случай — новый тип исключения непосредственно под корнем:
data MyException = ThisException | ThatException
deriving (Show, Typeable)
instance Exception MyException
Определения методов по умолчанию в классе Exception делают то, что нам нужно в этом случае. Теперь вы можете выбрасывать и перехватывать ThisException и ThatException как исключения:
*Main> throw ThisException `catch` \e -> putStrLn ("Caught " ++ show (e :: MyException))
Caught ThisException
В более сложных примерах вы можете захотеть определить целую иерархию исключений:
---------------------------------------------------------------------
-- Make the root exception type for all the exceptions in a compiler
data SomeCompilerException = forall e . Exception e => SomeCompilerException e
deriving Typeable
instance Show SomeCompilerException where
show (SomeCompilerException e) = show e
instance Exception SomeCompilerException
compilerExceptionToException :: Exception e => e -> SomeException
compilerExceptionToException = toException . SomeCompilerException
compilerExceptionFromException :: Exception e => SomeException -> Maybe e
compilerExceptionFromException x = do
SomeCompilerException a <- fromException x
cast a
---------------------------------------------------------------------
-- Make a subhierarchy for exceptions in the frontend of the compiler
data SomeFrontendException = forall e . Exception e => SomeFrontendException e
deriving Typeable
instance Show SomeFrontendException where
show (SomeFrontendException e) = show e
instance Exception SomeFrontendException where
toException = compilerExceptionToException
fromException = compilerExceptionFromException
frontendExceptionToException :: Exception e => e -> SomeException
frontendExceptionToException = toException . SomeFrontendException
frontendExceptionFromException :: Exception e => SomeException -> Maybe e
frontendExceptionFromException x = do
SomeFrontendException a <- fromException x
cast a
---------------------------------------------------------------------
-- Make an exception type for a particular frontend compiler exception
data MismatchedParentheses = MismatchedParentheses
deriving (Typeable, Show)
instance Exception MismatchedParentheses where
toException = frontendExceptionToException
fromException = frontendExceptionFromException
Теперь мы можем перехватить исключение MismatchedParentheses как MismatchedParentheses, SomeFrontendException или SomeCompilerException, но не другие типы, например, IOException:
*Main> throw MismatchedParenthesescatche -> putStrLn ("Caught " ++ show (e :: MismatchedParentheses)) Caught MismatchedParentheses *Main> throw MismatchedParenthesescatche -> putStrLn ("Caught " ++ show (e :: SomeFrontendException)) Caught MismatchedParentheses *Main> throw MismatchedParenthesescatche -> putStrLn ("Caught " ++ show (e :: SomeCompilerException)) Caught MismatchedParentheses *Main> throw MismatchedParenthesescatche -> putStrLn ("Caught " ++ show (e :: IOException)) *** Exception: MismatchedParentheses
Минимальное полное определение
Nothing
Методы
toException :: e -> SomeException Source
fromException :: SomeException -> Maybe e Source
displayException :: e -> String Source
Отобразить это значение исключения в удобном для человека формате.
Реализация по умолчанию: show.
Since: 4.8.0.0
Экземпляры
data IOException Source
Исключения, возникающие в монаде IO. IOException записывает более специфичный тип ошибки, описательную строку и, возможно, дескриптор, который использовался, когда ошибка была отмечена.
Экземпляры
data ArithException Source
Арифметические исключения.
Конструкторы
| Overflow | |
| Underflow | |
| LossOfPrecision | |
| DivideByZero | |
| Denormal | |
| RatioZeroDenominator | С тех пор как: 4.6.0.0 |
data ArrayException Источник
Исключения, сгенерированные операциями над массивами
Конструкторы
| IndexOutOfBounds String | Была предпринята попытка индексирования массива за пределами объявленных границ. |
| UndefinedElement String | Была предпринята попытка оценить элемент массива, который не был инициализирован. |
data AssertionFailed Источник
assert было применено к False.
Конструкторы
| AssertionFailed String |
Примеры
data SomeAsyncException Источник
Базовый класс для асинхронных исключений.
С тех пор как: 4.7.0.0
Конструкторы
| forall e . Exception e => SomeAsyncException e |
data AsyncException Источник
Асинхронные исключения.
Конструкторы
| StackOverflow | Стек текущего потока превысил свой лимит. Поскольку исключение было вызвано, стек потока, безусловно, будет ниже его лимита снова, но программист должен немедленно принять корректирующие меры. |
| HeapOverflow |
Купа программы достигает своего предела, и программа должна предпринять действия, чтобы уменьшить количество живых данных. Примечания:
|
| ThreadKilled | Это исключение вызывается другим потоком, вызывающим |
| UserInterrupt | Это исключение вызывается по умолчанию в главном потоке программы, когда пользователь запрашивает завершение программы с помощью обычного механизма (например, Control-C в консоли). |
asyncExceptionToException :: Exception e => e -> SomeException Источник
С тех пор как: 4.7.0.0
asyncExceptionFromException :: Exception e => SomeException -> Maybe e Источник
С тех пор как: 4.7.0.0
data NonTermination Источник
Вызывается, когда среда выполнения обнаруживает, что вычисление гарантированно не завершится. Обратите внимание, что нет гарантии, что среда выполнения заметит, гарантировано ли завершение любого данного вычисления или нет.
Конструкторы
| NonTermination |
Примеры
data NestedAtomically Источник
Вызывается, когда программа пытается вызвать atomically, из пакета stm, внутри другого вызова atomically.
Конструкторы
| NestedAtomically |
data ЗаблокированБессрочноНаMVar Source
Поток заблокирован на MVar, но других ссылок на MVar нет, поэтому он никогда не сможет продолжить.
Конструкторы
| ЗаблокированБессрочноНаMVar |
data ЗаблокированБессрочноНаSTM Source
Поток ожидает повторной попытки транзакции STM, но других ссылок на любые TVar нет, поэтому он никогда не сможет продолжить.
Конструкторы
| ЗаблокированБессрочноНаSTM |
data ПревышенПределВыделения Source
Данный поток превысил свой лимит выделения. См. setAllocationCounter и enableAllocationLimit.
С версии: 4.8.0.0
Конструкторы
| ПревышенПределВыделения |
Нет запустимых потоков, поэтому программа находится в тупике. Исключение Deadlock возникает только в главном потоке.
Конструкторы
| Тупик |
Типы
data ОшибкаОтсутствияМетода Source
Вызван метод класса без определения (ни стандартного, ни определения в соответствующем типе). String содержит информацию о вызываемом методе.
Конструкторы
| ОшибкаОтсутствияМетода Строка |
data ОшибкаСопоставленияШаблонов Source
Не удалось выполнить сопоставление шаблонов. String содержит информацию о местоположении шаблона в исходном коде.
Конструкторы
| ОшибкаСопоставленияШаблонов Строка |
data ОшибкаКонструктораЗаписи Source
Использовано неинициализированное поле записи. String содержит информацию о местоположении создания записи в исходном коде.
Конструкторы
| ОшибкаКонструктораЗаписи Строка |
data ОшибкаСелектораЗаписи Source
Селектор записи был применен к конструктору без соответствующего поля. Это возможно только с типом данных, имеющим несколько конструкторов, где некоторые поля присутствуют в одном, но отсутствуют в другом. String содержит информацию о местоположении селектора записи в исходном коде.
Конструкторы
| ОшибкаСелектораЗаписи Строка |
data ОшибкаОбновленияЗаписи Source
Обновление записи было выполнено на конструкторе без соответствующего поля. Это возможно только с типом данных, имеющим несколько конструкторов, где некоторые поля присутствуют в одном, но отсутствуют в другом. String содержит информацию о местоположении обновления записи в исходном коде.
Конструкторы
| ОшибкаОбновленияЗаписи Строка |
newtype ОшибкаВызова Source
Это выбрасывается, когда пользователь вызывает error. String — аргумент, переданный error.
Выбрасывание исключений
throw :: Исключение e => e -> a Источник
Выбросить исключение. Исключения могут быть выброшены из чисто функционального кода, но могут быть перехвачены только внутри IO монады.
throwIO :: Исключение e => e -> IO a Источник
Вариант throw, который может быть использован только внутри IO монады.
Хотя throwIO имеет тип, являющийся экземпляром типа throw, эти две функции немного отличаются:
throw e `seq` x ===> throw e throwIO e `seq` x ===> x
Первый пример вызовет исключение e, а второй — нет. На самом деле, throwIO вызовет исключение только при использовании внутри IO монады. Следует использовать вариант throwIO вместо throw для выброса исключения внутри IO монады, так как он гарантирует порядок с другими IO операциями, в то время как throw этого не гарантирует.
ioError :: IOError -> IO a Источник
Выбросить IOError в IO монаде.
throwTo :: Исключение e => ThreadId -> e -> IO () Источник
throwTo вызывает произвольное исключение в целевом потоке (только GHC).
Доставка исключений синхронизируется между исходным и целевым потоком: throwTo не возвращается до тех пор, пока исключение не будет вызвано в целевом потоке. Таким образом, вызывающий поток может быть уверен, что целевой поток получил исключение. Доставка исключений также атомарна относительно других исключений. Атомарность — полезное свойство при работе с гонками: например, если два потока могут убить друг друга, гарантируется, что только один из потоков сможет убить другого.
Любая работа, которую выполнял целевой поток, когда было вызвано исключение, не теряется: вычисление приостанавливается до тех пор, пока другой поток не потребует его.
Если целевой поток в настоящее время выполняет вызов внешней функции, исключение не будет выброшено (и, следовательно, throwTo не вернется) до завершения вызова. Это справедливо независимо от того, находится ли вызов внутри mask или нет. Однако в GHC вызов внешней функции может быть помечен как interruptible, в этом случае throwTo заставит RTS попытаться заставить вызов вернуть значение; см. документацию GHC для получения дополнительных сведений.
Важное примечание: поведение throwTo отличается от описанного в статье «Асинхронные исключения в Haskell» (http://research.microsoft.com/~simonpj/Papers/asynch-exns.htm). В статье throwTo является асинхронным; но реализация библиотеки использует более синхронный дизайн, в котором throwTo не возвращается до тех пор, пока исключение не будет получено целевым потоком. В статье (раздел 9) обсуждается компромисс.
Как и любая блокирующая операция, throwTo может быть прервана (см. раздел 5.3 статьи). Однако, в отличие от других прерывистых операций, throwTo всегда может быть прервана, даже если она фактически не блокирует.
Нет гарантии, что исключение будет доставлено немедленно, хотя среда выполнения постарается обеспечить, чтобы не происходило произвольных задержек. В GHC исключение может быть выброшено только тогда, когда поток достигает безопасной точки, где безопасная точка — это момент выделения памяти. Некоторые циклы не выполняют выделения памяти внутри цикла и, следовательно, не могут быть прерваны throwTo.
Если цель throwTo — вызывающий поток, поведение аналогично throwIO, за исключением того, что исключение выбрасывается как асинхронное исключение. Это означает, что если есть окружающее чистое вычисление, что имеет место, если текущая операция ввода-вывода находится внутри unsafePerformIO или unsafeInterleaveIO, это вычисление не заменяется исключением постоянно, а приостанавливается так, как будто оно получило асинхронное исключение.
Обратите внимание, что если throwTo вызывается с текущим потоком в качестве цели, исключение будет выброшено, даже если поток в настоящее время находится внутри mask или uninterruptibleMask.
Перехват исключений
Существуют несколько функций для перехвата и проверки исключений; все они могут использоваться только внутри IO монады.
Вот практическое правило для выбора функции перехвата:
- Если вам нужно выполнить некоторую очистку в случае возникновения исключения, используйте
finally,bracketилиonException. - Чтобы восстановиться после исключения и сделать что-то еще, лучшим выбором является использование одной из функций семейства
try. - ... если вы восстанавливаетесь после асинхронного исключения, используйте
catchилиcatchJust.
Разница между использованием try и catch для восстановления заключается в том, что в catch обработчик находится внутри неявной mask (см. «Асинхронные исключения»), что важно при перехвате асинхронных исключений, но при перехвате других типов исключений это не нужно. Кроме того, возможно случайно остаться внутри неявной mask с помощью хвостового вызова вместо возвращения из обработчика, поэтому рекомендуется использовать try вместо catch для обычного восстановления от исключений.
Типичное использование tryJust для восстановления выглядит так:
do r <- tryJust (guard . isDoesNotExistError) $ getEnv "HOME"
case r of
Left e -> ...
Right home -> ...
Перехват всех исключений
Можно перехватить все исключения, используя тип SomeException.
catch f (\e -> ... (e :: SomeException) ...)
Однако, обычно этого делать не нужно!
Например, предположим, что вы хотите прочитать файл, но если он не существует, продолжить, как если бы он содержал "". Вы можете быть искушены просто перехватить все исключения и вернуть "" в обработчике. Однако это имеет множество нежелательных последствий. Например, если пользователь нажмет Ctrl+C в подходящий момент, исключение UserInterrupt будет перехвачено, и программа продолжит работу, считая, что файл содержит "". Аналогично, если другой поток попытается убить поток, читающий файл, исключение ThreadKilled будет проигнорировано.
Вместо этого вы должны перехватывать только те исключения, которые вам действительно нужны. В этом случае это, вероятно, будет более конкретно, чем просто «любое исключение ввода-вывода»; ошибки разрешения, скорее всего, также должны обрабатываться по-разному. Вместо этого вы, вероятно, захотите что-то вроде:
e <- tryJust (guard . isDoesNotExistError) (readFile f) let str = either (const "") id e
Иногда вам действительно нужно перехватить любой тип исключения. Однако в большинстве случаев это делается для того, чтобы выполнить некоторую очистку; вас не интересует само исключение. Например, если вы открываете файл, вам нужно снова закрыть его, независимо от того, выполняется обработка файла нормально или выбрасывается исключение. Однако в этих случаях вы можете использовать функции, такие как bracket, finally и onException, которые никогда не передают вам исключение, а просто вызывают функции очистки в соответствующие моменты.
Но иногда вам действительно нужно перехватить любое исключение и увидеть, что это за исключение. Один пример — в самом верхнем уровне программы, вы можете захотеть перехватить любое исключение, напечатать его в журнал или на экран, а затем корректно выйти. В этих случаях вы можете использовать catch (или одну из других функций перехвата исключений) с типом SomeException.
Функции catch
Аргументы
| :: Исключение e | |
| => IO a | Выполняемое вычисление |
| -> (e -> IO a) | Обработчик, вызываемый при возникновении исключения |
| -> IO a |
Это самая простая из функций перехвата исключений. Она принимает один аргумент, выполняет его, и если возникает исключение, выполняется «обработчик» со значением исключения в качестве аргумента. В противном случае возвращается результат как обычно. Например:
catch (readFile f)
(\e -> do let err = show (e :: IOException)
hPutStr stderr ("Warning: Couldn't open " ++ f ++ ": " ++ err)
return "")
Обратите внимание, что нам нужно указать тип для e, иначе программа не пройдёт типизацию, так как тип является неоднозначным. Хотя можно перехватывать исключения любого типа, см. раздел «Перехват всех исключений» (в Control.Exception) для объяснения проблем, связанных с этим.
Для перехвата исключений в чистых (не-IO) выражениях, см. функцию evaluate.
Обратите внимание, что из-за неопределённого порядка вычислений в Haskell выражение может выбросить одно из нескольких возможных исключений: рассмотрите выражение (error "urk") + (1 `div` 0). Выбросит ли выражение ErrorCall "urk", или DivideByZero?
Ответ — «возможно, выбросит любое из них»; выбор не детерминирован. Если вы ловите исключения любого типа, то вы можете поймать любое из них. Если вы вызываете catch с типом IO Int -> (ArithException -> IO Int) -> IO Int, то обработчик может быть запущен с аргументом DivideByZero, или исключение ErrorCall "urk" может быть передано дальше. Если вы вызовете его снова, вы можете получить противоположное поведение. Это нормально, потому что catch — это IO вычисление.
catches :: IO a -> [Handler a] -> IO a Source
Иногда вам нужно поймать два разных типа исключений. Вы можете сделать что-то вроде
f = expr `catch` \ (ex :: ArithException) -> handleArith ex
`catch` \ (ex :: IOException) -> handleIO ex
Однако у этого подхода есть несколько проблем. Во-первых, наличие двух обработчиков исключений неэффективно. Однако более серьёзная проблема заключается в том, что второй обработчик исключений перехватит исключения из первого, например, в приведённом выше примере, если handleArith выбросит исключение IOException, то второй обработчик исключений его перехватит.
Вместо этого мы предоставляем функцию catches, которая используется следующим образом:
f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
Handler (\ (ex :: IOException) -> handleIO ex)]
Вам это понадобится при использовании catches.
Аргументы
| :: Exception e | |
| => (e -> Maybe b) | Предикат для выбора исключений |
| -> IO a | Вычисление, которое нужно выполнить |
| -> (b -> IO a) | Обработчик |
| -> IO a |
Функция catchJust похожа на catch, но она принимает дополнительный аргумент, который является предикатом для исключений, функцией, выбирающей типы исключений, которые нас интересуют.
catchJust (\e -> if isDoesNotExistErrorType (ioeGetErrorType e) then Just () else Nothing)
(readFile f)
(\_ -> do hPutStrLn stderr ("No such file: " ++ show f)
return "")
Любые другие исключения, которые не соответствуют предикату, повторно поднимаются и могут быть перехвачены внешним catch, catchJust, и т. д.
Функции handle
handle :: Exception e => (e -> IO a) -> IO a -> IO a Source
Вариант catch с переставленными аргументами; полезно в ситуациях, когда код обработчика короче. Например:
do handle (\NonTermination -> exitWith (ExitFailure 1)) $
...
handleJust :: Exception e => (e -> Maybe b) -> (b -> IO a) -> IO a -> IO a Source
Вариант catchJust с переставленными аргументами (см. handle).
Функции try
try :: Exception e => IO a -> IO (Either e a) Source
Аналогично catch, но возвращает результат типа Either, который является (Right a), если не было поднято исключение типа e, или (Left ex), если исключение типа e было поднято, и его значение равно ex. Если поднято исключение другого типа, оно будет передано вверх следующему обработчику исключений.
try a = catch (Right `liftM` a) (return . Left)
tryJust :: Exception e => (e -> Maybe b) -> IO a -> IO (Either b a) Source
Вариант try с предикат для выбора исключений (см. catchJust). Если исключение не соответствует предикату, оно повторно генерируется.
Функция evaluate
Принудительно вычисляет свой аргумент до слабой нормальной формы при выполнении результирующего IO действия. Может быть использован для упорядочения вычислений относительно других IO операций; его семантика задаётся
evaluate x `seq` y ==> y evaluate x `catch` f ==> (return $! x) `catch` f evaluate x >>= f ==> (return $! x) >>= f
Примечание: первое уравнение подразумевает, что (evaluate x) не равно (return $! x). Правильное определение
evaluate x = (return $! x) >>= return
Функция mapException
mapException :: (Exception e1, Exception e2) => (e1 -> e2) -> a -> a Source
Эта функция отображает одно исключение в другое, как предложено в статье «Семантика неточных исключений».
Асинхронные исключения
Асинхронные исключения так называются потому, что они возникают из-за внешних влияний и могут быть подняты в любой момент во время выполнения. StackOverflow и HeapOverflow — два примера системных асинхронных исключений.
Однако основным источником асинхронных исключений является throwTo:
throwTo :: ThreadId -> Exception -> IO ()
throwTo (также killThread ) позволяет одному работающему потоку генерировать произвольное исключение в другом потоке. Таким образом, исключение является асинхронным по отношению к целевому потоку, который мог выполнять любые действия в момент получения исключения. Необходимо проявлять особую осторожность с асинхронными исключениями; очень легко ввести гонки чрезмерным использованием throwTo.
Управление асинхронными исключениями
Следующие функции позволяют потоку управлять доставкой асинхронных исключений во время критической области.
mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b Source
Выполняет вычисление IO с замаскированными асинхронными исключениями. То есть любой поток, пытающийся генерировать исключение в текущем потоке с помощью throwTo , будет заблокирован до тех пор, пока асинхронные исключения не будут вновь размаскированы.
Аргумент, передаваемый в mask , — это функция, которая принимает в качестве аргумента другую функцию, которую можно использовать для восстановления состояния маскирования в контексте вычисления с маской. Например, распространённый способ использования mask — защита доступа к ресурсу:
mask $ \restore -> do
x <- acquire
restore (do_something_with x) `onException` release
release
Этот код гарантирует, что acquire сочетается с release , маскируя асинхронные исключения для критических частей. (Вместо написания этого кода самим лучше использовать bracket, который абстрагирует общий шаблон).
Обратите внимание, что действие restore , переданное в аргумент mask , необязательно снимает маску асинхронных исключений, оно просто восстанавливает состояние маскирования до состояния окружающего контекста. Таким образом, если асинхронные исключения уже замаскированы, mask не может быть использован для размаскирования исключений. Это сделано для того, чтобы при вызове библиотечной функции с замаскированными исключениями можно было быть уверенным, что библиотечный вызов не сможет снова размаскировать исключения. Если вы пишете библиотечный код и вам нужно использовать асинхронные исключения, единственный способ — создать новый поток; см. forkIOWithUnmask.
Асинхронные исключения всё равно могут быть получены во время маскирования, если замаскированный поток блокируется определёнными способами; см. Control.Exception.
Потоки, созданные с помощью forkIO , наследуют состояние MaskingState от родительского; то есть, чтобы запустить поток в состоянии MaskedInterruptible , используйте mask_ $ forkIO ... . Это особенно полезно, если вам нужно установить обработчик исключений в порождённом потоке до получения любых асинхронных исключений. Чтобы создать новый поток в незамаскированном состоянии, используйте forkIOUnmasked.
Как mask, но не передает действие restore аргументу.
uninterruptibleMask :: ((forall a. IO a -> IO a) -> IO b) -> IO b Source
Как mask, но замаскированное вычисление не прерывается (см. Control.Exception). ЭТО НУЖНО ИСПОЛЬЗОВАТЬ С ОСТОРОЖНОСТЬЮ, потому что если поток, выполняющийся в uninterruptibleMask, блокируется по какой-либо причине, то поток (и, возможно, программа, если это главный поток) будет неотзываемым и неуничтожимым. Эта функция должна потребоваться только в том случае, если вам нужно замаскировать исключения вокруг прерываемой операции, и вы можете гарантировать, что прерываемая операция будет блокироваться только на короткий промежуток времени.
uninterruptibleMask_ :: IO a -> IO a Source
Как uninterruptibleMask, но не передает действие restore аргументу.
data MaskingState Source
Описывает поведение потока при получении асинхронного исключения.
Конструкторы
| Unmasked | асинхронные исключения не замаскированы (обычное состояние) |
| MaskedInterruptible | состояние во время |
| MaskedUninterruptible | состояние во время |
Примеры
getMaskingState :: IO MaskingState Source
Возвращает MaskingState для текущего потока.
allowInterrupt :: IO () Source
При вызове внутри mask, эта функция разрешает поднятие замаскированного асинхронного исключения, если оно существует. Она эквивалентна выполнению прерываемой операции (см. ), но не включает в себя фактическое блокирование.
При вызове вне mask, или внутри uninterruptibleMask, эта функция не оказывает никакого влияния.
Since: 4.4.0.0
Применение mask к обработчику исключений
Существует неявное mask вокруг каждого обработчика исключений в вызове одной из функций catch. Это потому, что это то, что вы хотите в большинстве случаев — это устраняет распространённую гонку при запуске обработчика исключений, так как может не быть обработчика исключений в стеке для обработки другого исключения, если оно прибудет сразу. Если асинхронные исключения замаскированы при входе в обработчик, у нас есть время установить новый обработчик исключений до прерывания. Если бы это не было по умолчанию, нужно было бы написать что-то вроде
mask $ \restore ->
catch (restore (...))
(\e -> handler)
Если вам нужно снова размаскировать асинхронные исключения в обработчике исключений, restore можно использовать и там.
Обратите внимание, что try и аналогичные функции *не* имеют подобного по умолчанию, потому что в этом случае нет обработчика исключений. Не используйте try для восстановления после асинхронного исключения.
Прерываемые операции
Некоторые операции являются *прерываемыми*, что означает, что они могут получать асинхронные исключения даже в рамках mask. Любая функция, которая сама может блокироваться, определяется как прерываемая; сюда входят takeMVar (но не tryTakeMVar), и большинство операций, выполняющих какое-либо ввод-вывод с внешним миром. Причина наличия прерываемых операций заключается в том, чтобы мы могли писать вещи вроде
mask $ \restore -> do
a <- takeMVar m
catch (restore (...))
(\e -> ...)
Если бы takeMVar не было прерываемым, то эта конкретная комбинация могла привести к тупику, потому что поток сам был бы заблокирован в состоянии, где он не может получить никаких асинхронных исключений. Однако, если takeMVar прерывается, мы можем быть уверены в том, что поток может получить исключения прямо до точки, когда takeMVar завершится успешно. Аналогичные аргументы применяются к другим прерываемым операциям, таким как openFile.
Полезно рассматривать mask не как способ полностью предотвратить асинхронные исключения, а как способ переключения с асинхронного режима на режим опроса. Основная проблема с асинхронными исключениями заключается в том, что они обычно могут возникнуть где угодно, но внутри mask асинхронное исключение поднимается только операциями, которые прерываемы (или вызывают другие прерываемые операции). Во многих случаях эти операции сами могут поднимать исключения, такие как ошибки ввода-вывода, поэтому вызывающий обычно будет готов обрабатывать возникающие из операции исключения. Чтобы выполнить явный опрос асинхронных исключений внутри mask, используйте allowInterrupt.
Иногда слишком сложно обрабатывать исключения посреди критического фрагмента кода состояния. Существует три способа обработки этой ситуации:
- Используйте STM. Поскольку транзакция всегда либо полностью выполняется, либо не выполняется, транзакции являются хорошим способом поддержания инвариантов состояния при наличии асинхронных (и, в действительности, синхронных) исключений.
- Используйте
mask, и избегайте прерываемых операций. Для этого нужно знать, какие операции прерываемы. Невозможно знать для любой данной функции библиотеки, может ли она вызвать прерываемую операцию внутри; поэтому вместо этого мы приводим список гарантированно не прерываемых операций ниже. - Используйте
uninterruptibleMask. Это вообще не рекомендуется, если вы не можете гарантировать, что любые прерываемые операции, вызванные в рамкахuninterruptibleMask, могут быть заблокированы только на короткое время. В противном случаеuninterruptibleMask— это хороший способ заставить вашу программу зависнуть и не реагировать на пользовательские прерывания.
Следующие операции гарантированно не являются прерываемыми:
- операции с
IORefиз Data.IORef - транзакции STM, которые не используют
retry - все из модулей
Foreign - все из
Control.Exceptionза исключениемthrowTo -
tryTakeMVar,tryPutMVar,isEmptyMVar -
takeMVarеслиMVarопределенно полна, и, наоборот,putMVarеслиMVarопределенно пуста -
newEmptyMVar,newMVar -
forkIO,forkIOUnmasked,myThreadId
Утверждения
assert :: Bool -> a -> a Source
Если первый аргумент оценивается как True, то результатом является второй аргумент. В противном случае поднимается исключение AssertionFailed, содержащее String с именем файла и номером строки вызова assert.
Утверждения обычно можно включить или выключить с помощью флага компилятора (для GHC утверждения обычно включены, если оптимизация не включена с флагом -O или установлен параметр -fignore-asserts). Когда утверждения выключены, первый аргумент к assert игнорируется, а второй аргумент возвращается как результат.
Утилиты
Аргументы
| :: IO a | вычисление, выполняемое в первую очередь ("получение ресурса") |
| -> (a -> IO b) | вычисление, выполняемое в последнюю очередь ("освобождение ресурса") |
| -> (a -> IO c) | вычисление, выполняемое между ними |
| -> IO c |
Когда вам нужно получить ресурс, выполнить с ним некоторую работу и затем освободить ресурс, рекомендуется использовать bracket, поскольку bracket установит необходимый обработчик исключений для освобождения ресурса в случае возникновения исключения во время вычисления. Если возникает исключение, то bracket поднимет исключение повторно (после выполнения освобождения).
Типичный пример — открытие файла:
bracket
(openFile "filename" ReadMode)
(hClose)
(\fileHandle -> do { ... })
Аргументы для bracket находятся в этом порядке, чтобы мы могли частично применить его, например:
withFile name mode = bracket (openFile name mode) hClose
bracket_ :: IO a -> IO b -> IO c -> IO c Source
Вариант bracket, где возвращаемое значение от первого вычисления не требуется.
Аргументы
| :: IO a | вычисление, которое выполняется в первую очередь ("получение ресурса") |
| -> (a -> IO b) | вычисление, которое выполняется последним ("освобождение ресурса") |
| -> (a -> IO c) | вычисление, которое выполняется между ними |
| -> IO c |
Подобно bracket, но выполняет только последнее действие, если в промежуточном вычислении возникло исключение.
Аргументы
| :: IO a | вычисление, которое выполняется в первую очередь |
| -> IO b | вычисление, которое выполняется после (даже если возникло исключение) |
| -> IO a |
Специализированный вариант bracket с только вычислением, выполняемым после.
onException :: IO a -> IO b -> IO a Source
Подобно finally, но выполняет только последнее действие, если в вычислении возникло исключение.
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/7.10.3/docs/html/libraries/base-4.8.2.0/Control-Exception.html