2015-10-19 30 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