Что вызвало ошибку "отложенное чтение по закрытому дескриптору"?

Я только что установил GHC из последних источников, и теперь моя программа выдает мне сообщение об ошибке "отложенное чтение по закрытому дескриптору". Что это значит?

1 ответ

Основной ленивый примитив ввода / вывода, 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 символов и распечатывает первые двенадцать. Затем он закрывает файл, когда 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, это поведение меняется. Когда вы закрываете дескриптор, который читаете лениво, теперь он фактически помещает исключение в конец буфера вместо обычного :"", Поэтому, если вы попытаетесь проверить строку за пределами точки, где файл был закрыт, вы получите сообщение об ошибке.

Другие вопросы по тегам