2015-10-19 33 views
1

写入标准输入并从子进程的标准输出中读取而不阻塞的最佳方式是什么?Haskell中的子进程的非阻塞读取

子进程通过System.IO.createProcess创建,该子进程返回用于写入和读取子进程的句柄。写作和阅读以文本格式完成。

例如,我在做非阻塞读取时的最佳尝试是timeout 1 $ hGetLine out,如果不存在要读取的行,则返回Just "some line"Nothing。然而,这对我来说似乎是一种破绽,所以我正在寻找更“标准”的方式。

感谢

+0

你想要这个函数有什么语义?如果进程将字符“ABC”写入“out”,然后程序调用“hGetLineNonBlocking”,读取字符“ABC”,但由于尚未读取换行符而无法返回“Just”它不能阻止,直到有更多的字符像“hGetLine”(显然)。你扔掉那条线吗?这几乎肯定是错误的。我怀疑在这种情况下,你会阻止,等待剩下的线路。如果是这样,只需在用'hGetLine'读取之前检查句柄是否为'hReady'。 – user2407038

+0

@ user2407038对不起,但我不明白你的评论,因为执行'timeout 1 $ hGetLine ...'总是返回'Just',如果有整行被读取,否则返回'Nothing'。 – mljrg

+0

为了'hGetLine'读一行,它必须按顺序读取字符,直到它到达一个换行符,然后返回。但是,如果'hGetLine'读取了一堆字符,但没有换行符,那么这行就没有完成 - 所以hGetLine会阻塞,直到有更多的字符。如果你的函数是合理的,它不会读取缓冲区中的字符并丢弃它们,但它不能阻止 - 它对已经读取的字符有什么作用?它是否返回部分行,哪个*不是由换行符终止的? – user2407038

回答

2

下面是如何在由@jberryman提到时尚衍生进程交互的一些例子。

该程序与脚本./compute简单地读取从stdin线路中的形式和<x> <y>ÿ秒的延迟之后返回X + 1相互作用。更多详情,请致电this gist

与产生的进程进行交互时有许多警告。为了避免“遭受缓冲”,每次发送输入时都需要刷新输出管道,并且每次发送响应时,产生的进程都需要刷新stdout。如果您发现stdout没有及时刷新,则可以通过伪tty与进程交互。

此外,这些示例假定关闭输入管道将导致产卵过程终止。如果不是这种情况,你将不得不发送一个信号来确保终止。

以下是示例代码 - 请参阅样本调用结尾处的main例程。

import System.Environment 
import System.Timeout (timeout) 
import Control.Concurrent 
import Control.Concurrent (forkIO, threadDelay, killThread) 
import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar) 

import System.Process 
import System.IO 

-- blocking IO 
main1 cmd tmicros = do 
    r <- createProcess (proc "./compute" []) { std_out = CreatePipe, std_in = CreatePipe } 
    let (Just inp, Just outp, _, phandle) = r 

    hSetBuffering inp NoBuffering 
    hPutStrLn inp cmd  -- send a command 

    -- block until the response is received 
    contents <- hGetLine outp 
    putStrLn $ "got: " ++ contents 

    hClose inp   -- and close the pipe 
    putStrLn "waiting for process to terminate" 
    waitForProcess phandle 

-- non-blocking IO, send one line, wait the timeout period for a response 
main2 cmd tmicros = do 
    r <- createProcess (proc "./compute" []) { std_out = CreatePipe, std_in = CreatePipe } 
    let (Just inp, Just outp, _, phandle) = r 

    hSetBuffering inp NoBuffering 
    hPutStrLn inp cmd -- send a command, will respond after 4 seconds 

    mvar <- newEmptyMVar 
    tid <- forkIO $ hGetLine outp >>= putMVar mvar 

    -- wait the timeout period for the response 
    result <- timeout tmicros (takeMVar mvar) 
    killThread tid 

    case result of 
    Nothing -> putStrLn "timed out" 
    Just x -> putStrLn $ "got: " ++ x 

    hClose inp   -- and close the pipe 
    putStrLn "waiting for process to terminate" 
    waitForProcess phandle 

-- non-block IO, send one line, report progress every timeout period 
main3 cmd tmicros = do 
    r <- createProcess (proc "./compute" []) { std_out = CreatePipe, std_in = CreatePipe } 
    let (Just inp, Just outp, _, phandle) = r 

    hSetBuffering inp NoBuffering 
    hPutStrLn inp cmd -- send command 

    mvar <- newEmptyMVar 
    tid <- forkIO $ hGetLine outp >>= putMVar mvar 

    -- loop until response received; report progress every timeout period 
    let loop = do result <- timeout tmicros (takeMVar mvar) 
       case result of 
        Nothing -> putStrLn "still waiting..." >> loop 
        Just x -> return x 
    x <- loop 
    killThread tid 

    putStrLn $ "got: " ++ x 

    hClose inp   -- and close the pipe 
    putStrLn "waiting for process to terminate" 
    waitForProcess phandle 

{- 

Usage: ./prog which delay timeout 

    where 
    which = main routine to run: 1, 2 or 3 
    delay = delay in seconds to send to compute script 
    timeout = timeout in seconds to wait for response 

E.g.: 

    ./prog 1 4 3 -- note: timeout is ignored for main1 
    ./prog 2 2 3 -- should timeout 
    ./prog 2 4 3 -- should get response 
    ./prog 3 4 1 -- should see "still waiting..." a couple of times 

-} 

main = do 
    (which : vtime : tout : _) <- fmap (map read) getArgs 
    let cmd = "10 " ++ show vtime 
     tmicros = 1000000*tout :: Int 
    case which of 
    1 -> main1 cmd tmicros 
    2 -> main2 cmd tmicros 
    3 -> main3 cmd tmicros 
    _ -> error "huh?" 
+1

非常感谢您的全面回答:这是一个非常好的例子。只是几个问题:你不需要'关闭输出'?另外,你不是在执行'terminateProcess phandle',这是因为'hClose inp'预计会关闭外部进程吗?如果产生的进程挂起,你的解决方案将如何强制停止? – mljrg