我刚刚从最新来源安装了 GHC,现在我的程序给了我一条关于“关闭句柄延迟读取”的错误消息。这是什么意思?
1 回答
基本的惰性 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 并返回一个常量(例如()
),您无需担心这一点。如果您需要它从惰性读取中产生一个真正的值,您需要确保在返回之前充分强制该值。在上面的示例中,您可以使用模块中的函数或运算符将字符串强制为“正常形式” 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)
作为替代方案,要考虑的另一个功能是readFile
。readFile
保持文件打开,直到它完成读取文件。但是,如果您知道您实际上将需要文件的全部内容,则应该只使用readFile
—— 否则您可能会泄漏文件描述符。
历史
根据 Haskell 报告,一旦句柄关闭,字符串的内容就固定了。
过去,GHC 只是在句柄关闭时缓冲的任何内容的末尾结束字符串。例如,如果您在关闭句柄之前检查了字符串的前 10 个字符,并且 GHC 已经缓冲了额外的 634 个字符,但没有到达文件末尾,那么您将得到一个 644 个字符的普通字符串。这是新用户混淆的常见来源,也是生产代码中偶尔出现的错误来源。
从 GHC 7.10.1 开始,这种行为正在发生变化。当你关闭一个你懒惰读取的句柄时,它现在有效地将异常放在缓冲区的末尾,而不是通常的:""
. 因此,如果您尝试检查超出文件关闭点的字符串,您将收到一条错误消息。