我的代码使用了一个可以描述为指针的资源;为了简单起见,我将在这里使用void指针。资源必须在计算完成后关闭,因此该Control.Exception.bracket函数是确保发生错误时代码不会泄漏的自然选择:
run :: (Ptr () -> IO a) -> IO a
run action = bracket acquireResource closeResource action
-- no eta reduction for clarity
Run Code Online (Sandbox Code Playgroud)
此模式的缺点是资源将始终在action完成后关闭。AFAIU 这意味着不可能做类似的事情
cont <- run $ \ptr -> do
a <- someAction ptr
return (\x -> otherActionUsingResource ptr a x)
cont ()
Run Code Online (Sandbox Code Playgroud)
cont执行时资源已关闭。现在我的方法是使用ForeignPtr代替:
run' :: (ForeignPtr () -> IO a) -> IO a
run' action = do
ptr <- acquireResource
foreignPtr <- newForeignPtr closeResourceFunPtr ptr
action foreignPtr
Run Code Online (Sandbox Code Playgroud)
现在看来,这大致相当于第一个版本,除了细微的打字差异和资源关闭延迟之外。然而,我确实想知道这是真的,还是我错过了什么。某些错误条件是否会导致这两个版本产生不同的结果?以这种方式使用ForeignPtr安全吗?
如果您想这样做,我建议您避免这样做run',这会让您看起来像是要关闭资源。而是做这样的事情。
acquire :: IO (ForeignPtr ())
acquire action = mask $ \unmask -> do
ptr <- unmask acquireResource
newForeignPtr closeResourceFunPtr ptr
Run Code Online (Sandbox Code Playgroud)
正如 Carl 在评论中指出的那样,在获取资源和安装终结器以关闭资源之间屏蔽异常非常重要;否则,可能会在两者之间传递异步异常并导致资源泄漏。
此类问题的挑战在于您将其留给用户和/或垃圾收集器来确保资源得到释放。基于原始的Ptr代码使生命周期变得明确。现在不是了。许多人认为,明确的生命周期对于关键资源来说更好。什么ForeignPtr给你,由GC自动完成,这些人认为设计很糟糕。所以要仔细考虑一下!malloc这是您最终只想释放的廉价资源(例如一点ed 内存)吗?或者它是您真正想要确定的昂贵的东西(例如文件描述符)?
旁注:Ptr ()并且ForeignPtr ()不是很惯用。通常类型参数应该是 Haskell 类型,表示所指向的内容。
| 归档时间: |
|
| 查看次数: |
225 次 |
| 最近记录: |