是什么造成了这个"关闭句柄延迟阅读"错误?

时间:2014-11-15 18:44:29

标签: haskell ghc lazy-io

我刚从最新的来源安装了GHC,现在我的程序给了我一条关于“关闭句柄的延迟读取”的错误消息。这是什么意思?

1 个答案:

答案 0 :(得分:10)

基本的懒惰I / O原语hGetContents懒惰地生成String - 它只根据需要从句柄读取,以生成程序实际需要的字符串部分。但是,一旦句柄关闭,就不再可以从句柄中读取,如果您尝试检查尚未读取的字符串的一部分,您将获得此异常。例如,假设你写了

main = do
  most <- withFile "myfile" ReadMode
                (\h -> do
                         s <- hGetContents h
                         let (first12,rest) = splitAt 12 s
                         print first12
                         return rest)
  putStrLn most

GHC打开myfile并将其设置为懒惰读取我们绑定到s的字符串。它 实际上开始从文件中读取。然后它设置一个延迟计算,以便在12个字符后分割字符串。然后print强制进行计算,GHC读取一个myfile块至少12个字符,然后打印出前12个字符。然后在withFile完成时关闭文件,并尝试打印其余部分。如果文件长于缓冲的块GHC,则一旦到达块的末尾,您将获得延迟的读取异常。

如何避免此问题

您需要确保在关闭文件或从withFile返回之前,您实际上已经阅读了所需的所有内容。如果传递给withFile的函数只执行一些IO并返回一个常量(例如()),则不必担心这一点。如果您需要通过延迟读取生成实际值,则需要确保在返回之前强制该值。在上面的示例中,您可以强制字符串为&#34;普通形式&#34;使用Control.DeepSeq模块中的函数或运算符:

return $!! rest

这确保在withFile关闭文件之前实际读取字符串的其余部分。如果返回的是从文件内容计算的某个值,$!!方法也可以很好地工作,只要它是NFData类的实例。在这种情况下,以及其他许多方面,只需将用于处理文件内容的其余代码移动到传递给withFile的函数中就更好了,如下所示:

main = withFile "myfile" ReadMode
            (\h -> do
                     s <- hGetContents h
                     let (first12,rest) = splitAt 12 s
                     print first12
                     putStrLn rest)

另一个需要考虑的功能是readFilereadFile保持文件处于打开状态,直到读完文件为止。您应该只使用readFile,但是,如果您知道您实际上会要求文件的全部内容 - 否则您可能会泄漏文件描述符。

历史

根据Haskell报告,一旦句柄关闭,字符串的内容就会固定。

在过去,GHC只是在句柄关闭时缓冲的任何内容结束时简单地结束了字符串。例如,如果您在关闭句柄之前检查了字符串的前10个字符,并且GHC已经缓冲了额外的634个字符,但没有到达文件的末尾,那么您将得到一个包含644个字符的普通字符串。这是新用户之间混淆的常见原因,也是生产代码中偶尔出现的错误来源。

从GHC 7.10.1开始,这种行为正在发生变化。当你关闭一个懒惰的句柄时,它现在有效地在缓冲区的末尾放置一个异常而不是通常的:""。因此,如果您尝试检查超出文件关闭点的字符串,您将收到错误消息。