Я хочу сделать «асинхронное» чтение stdin
в течение нескольких секунд. Я делаю это, разветвляя getContents
и пишу Чану. Через 5 секунд я убиваю ветку и читаю канал.
Насколько я понимаю, приведенный ниже код должен просто печатать все, что находится в chan
, и завершаться, но он продолжает ждать ввода, и для завершения необходимо нажать ^C
. Это поведение, которое у вас есть, когда вы делаете getContents >>= print
самостоятельно, поэтому у меня есть два предположения и понятия не имею о каждом:
getContent
продолжает работать, запрашивая дополнительные данные
-- OS: Ubuntu 22.04.1 LTS
-- Gnome Terminal
-- ghc 9.2.5
import Control.Concurrent.Chan ( newChan, readChan, writeChan, Chan )
import Control.Concurrent (threadDelay, forkIO, killThread)
import System.IO (getContents, BufferMode (..), stdin, hSetBuffering)
main :: IO ()
main = do
hSetBuffering stdin NoBuffering
chan <- newChan
putStrLn "start"
threadId <- forkIO $ getContents >>= writeChan chan
threadDelay 5000000
putStrLn "\nend"
killThread threadId
a <- readChan chan
print a
Некоторые соображения:
getLine
заставит его работать, только если нажата клавиша Enter. Но хотелось бы просто "стримить" stdin
в каналhSetBuffering stdin NoBuffering
обязательна, иначе программа зависнет (наверное ждет окончания ввода?)getContents'
вызывает thread blocked indefinitely in an MVar operation
, что, согласно документации , связано с тем, что канал пуст. Я думаю, getContents'
на самом деле никогда не заканчивается.ghc -threaded main.hs && ./main +RTS -N2
ничего не печатает и зависает до тех пор, пока не будет нажата ^C
(то же самое с -N1
, -N3
и т.д...)runghc main.hs
фактически напечатает все, что является stdin
в течение 5 секунд (то есть все, что находится в chan
), а затем зависнет.Просто для уточнения. Вот выходы:
> runghc main.hs
start
abc # user input
end
"abc^C" # ^C is done manually, to termiante the program and the last \" is for formatting purpose
> ghc -threaded main.hs && ./main +RTS -N2
start
abc # user input
end
^C # ^C is done manually, to termiante the program
Так что вопрос простой. Как мне завершить программу?
🤔 А знаете ли вы, что...
Haskell имеет множество расширений языка, которые позволяют разработчикам настраивать его поведение.
Тема getContents >>= writeChan chan
— это не бесконечный цикл, который постоянно добавляет контент в chan
. getContents
создает преобразователь, который вставляется в chan
, и поток почти мгновенно завершается. Затем в основном потоке readChan
получает этот преобразователь и print a
форсирует его. Это форсирование thunk, которое предлагает чтение stdin, поэтому ваша программа просто блокируется для большего ввода до тех пор, пока EOF или она не будет убита.
Что вы хотите сделать, так это явно взять небольшие биты ввода и записать их в канал. Однако в основном потоке канал не дает вам возможности узнать, когда он закончился. Обходной путь — использовать вместо этого IORef String
в качестве канала. Напишите в него, явно добавив к сохраненной строке, и readIORef
даст вам весь контент, который был написан до сих пор.
import Control.Concurrent.Chan ( newChan, readChan, writeChan, Chan )
import Control.Concurrent (threadDelay, forkIO, killThread)
import Control.Monad (forever)
import Data.IORef
import System.IO (getContents, BufferMode (..), stdin, hSetBuffering)
main :: IO ()
main = do
hSetBuffering stdin NoBuffering
buf <- newIORef []
putStrLn "start"
threadId <- forkIO $ forever $ do
c <- getChar
atomicModifyIORef' buf (\cs -> (c : cs, ()))
threadDelay 5000000
putStrLn "\nend"
killThread threadId
a <- reverse <$> readIORef buf
print a