我试着理解屏蔽异常在一般情况下是如何在一个简单的示例代码上工作的,下面是库源代码中的bracket
fn:
bracket before after thing =
mask $ \restore -> do
a <- before
r <- restore (thing a) `onException` after a
_ <- after a
return r
下面是我的“括号”版本,没有mask
myBracket before after thing = do
a <- before
r <- thing a `onException` after a
_ <- after a
pure r
我用下面这样的简单代码来测试它:
tid <- forkIO $ bracket acquire release work
treadDelay some
throwTo tid MyException
我在不同的时刻抛出自定义异常:当执行acquire
、work
和release
时,对于bracket
和myBracket
,我得到了相同的结果。
因此,我的问题是如何修改我的示例/测试代码,以便屏蔽/恢复在异常处理中的作用是可见的/明显的?
2条答案
按热度按时间kgsdhlau1#
如果在
acquire
过程中抛出异常,那么如果没有屏蔽,我们可能永远不会执行release
。运行时,我们可以看到
release
只打印了第二次,对于bracket
:z31licg02#
引用自"Parallel and Concurrent Programming in Haskell":
少量的操作,包括takeMVar,被指定为可中断的。2可中断的操作甚至可以在mask内部接收异步异常。
为什么要这样做?可以将掩码看作是 * 切换到轮询模式以查找异步异常 *。在掩码内部,异步异常不再是异步的,但某些操作仍然可以引发它们。换句话说,异步异常在掩码内部变为同步的。
在
mask
-using括号中,before
和after
操作如果碰巧使用了takeMVar
、threadDelay
或hClose
这样的可中断操作,仍然可能抛出异步异常。即使在mask
中也是如此!通常,可能使用户等待很长时间的IO
操作往往是可中断的。mask
确保的是异步异常不会发生在IO
操作之间的间隙,或者不会导致长时间等待的IO
操作。这使得异步异常更易于管理。例如,您可以使用try
处理它们,而不必担心try
本身可能会被中断。1在没有
mask
的bracket
版本中,在执行before
和thing
之间,或者在执行thing
和after
之间,可能会突然出现异步异常。在这两种情况下,after
都不会执行,并且括号中的资源将保持未释放状态。1也就是说,捕获的任何异步异常最终都应该重新引发。
注意,根据上面的内容,如果你把
threadDelay 10000000
作为before
传递,它可能会抛出一个异步异常,甚至在mask
内部也是如此!你有责任传递before
操作(即分配操作),这些操作在被中断时不会让未释放的资源挂起。像
openFile
这样的函数在这些情况下会做正确的事情,这一点,加上bracket
被正确屏蔽的知识,意味着用户不必担心。顺便说一下:我们说过
hClose
是可中断的。应该担心被中断的bracket
会留下未关闭的句柄吗?不,因为hClose
的合约说:hClose是Control. Exception中所描述的意义上的可中断操作。如果hClose在刷新其缓冲器的过程中被异步异常中断,则I/O设备(例如,文件)无论如何将被关闭。
因此,即使中断,
hClose
也将关闭句柄。这段代码使用
uninterruptibleMask_
(由于其倾向于使程序无响应,因此应非常小心地使用该函数)创建一个不可中断的threadDelay
,并将其传递给bracket
。掩码版本的
bracket
可以打印"after"
,无掩码版本则不能。这是因为,如果没有
mask
,抛出给线程的异常将在before
完成后立即出现,而没有时间安装onException
处理程序来确保清理。相比之下,对于
bracket
的屏蔽版本,异常直到我们调用restore
回调函数时才会出现,但此时onException
处理程序已经安装好了。