如何在Haskell中检索进程的输出而不阻塞线程

如何在Haskell中检索进程的输出而不阻塞线程,haskell,Haskell,在不阻塞的情况下,向stdin写入数据并从子进程的stdout读取数据的最佳方式是什么 子流程是通过System.IO.createProcess创建的,它返回用于写入和读取子流程的句柄。书写和阅读是以文本形式完成的 例如,我进行非阻塞读取的最佳尝试是超时1$hGetLine out,如果不存在要读取的行,它将返回仅“某行”或Nothing。然而,这对我来说似乎是一种黑客行为,所以我正在寻找一种更“标准”的方式 谢谢以下是一些示例,说明如何以@jberryman提到的方式与衍生流程交互 程序与脚

在不阻塞的情况下,向stdin写入数据并从子进程的stdout读取数据的最佳方式是什么

子流程是通过
System.IO.createProcess
创建的,它返回用于写入和读取子流程的句柄。书写和阅读是以文本形式完成的

例如,我进行非阻塞读取的最佳尝试是
超时1$hGetLine out
,如果不存在要读取的行,它将返回
仅“某行”
Nothing
。然而,这对我来说似乎是一种黑客行为,所以我正在寻找一种更“标准”的方式


谢谢

以下是一些示例,说明如何以@jberryman提到的方式与衍生流程交互

程序与脚本
/compute
交互,该脚本以
的形式从stdin中读取行,并在延迟y秒后返回x+1。更多详情请访问

当与派生的进程交互时,有许多警告。为了避免“受到缓冲的影响”,您需要在发送输入时刷新传出管道,而派生的进程需要在每次发送响应时刷新标准输出。如果您发现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?"
导入系统环境
导入系统。超时(超时)
导入控制。并发
导入控制。并发(forkIO、threadDelay、killThread)
import Control.Concurrent.MVar(newEmptyMVar、putMVar、takeMVar)
导入系统。进程
导入系统.IO
--阻塞IO
main1 cmd tmicros=do
r返回x
x main2 cmd tmicros
3->main3命令tmicros
_->错误“嗯?”

您希望此函数具有什么语义?如果进程将字符“ABC”写入
out
,然后您的程序调用
hGetLineOnBlocking
,读取字符“ABC”,但无法返回
只是
一些内容,因为尚未读取换行符;在出现更多类似于
hGetLine
(显然)的字符之前,它无法阻止。你会扔掉那一部分吗?这几乎肯定是错误的。我怀疑,在这种情况下,你确实想阻止,等待其余的线。如果是这样,在使用
hGetLine
@user2407038阅读之前,只需使用
hReady
检查句柄是否为空。抱歉,我不理解您的评论,因为如果要读取整行,执行
timeout 1$hGetLine…
总是返回
just
内容,否则,它将返回
Nothing
。为了让
hGetLine
读取一行,它必须按顺序读取字符,直到到达换行符,然后返回。但是,如果
hGetLine
读取了一组字符,但没有换行符,则该行未完成-因此hGetLine将阻塞,直到有更多字符为止。如果您的函数是合理的,它不会从缓冲区读取字符并丢弃它们,但它不能阻止-它对已读取的字符做什么?它是否返回不以换行符结尾的部分行?通常,您不会阻塞。相反,您生成一个线程,然后继续让它阻塞。另见。如果这解决了你的问题,我很乐意将其标记为副本;丹尼尔瓦格纳:埃里克的详细回答(见下文)更能回答我的问题。尽管如此,还是要感谢你提供了另一个问题的链接。非常感谢你的透彻回答:这是一个非常好的例子。只有几个问题:您不需要
hClose outp
?另外,您没有执行
terminateProcess phandle
,这是因为
hClose inp
预计会关闭外部进程吗?如果生成的进程挂起,您的解决方案将如何强制它停止?