简体   繁体   English

为什么 StandardOutput.Read() 永远不会返回? (僵局?)

[英]Why does StandardOutput.Read() never return? (deadlock?)

Using C#, I want to automate a third-party Windows command-line program.使用 C#,我想自动化第三方 Windows 命令行程序。 Usually, it is an interactive console, you send commands, it may prompt for details, send back a result and display a prompt to ask for more commands.通常,它是一个交互式控制台,您发送命令,它可能会提示详细信息、返回结果并显示提示以询问更多命令。 Typically:通常:

c:\>console_access.exe
Prompt> version
2.03g.2321
Prompt> 

I used .NET classes Process and ProcessStartInfo along with redirections of stdin/stdout/stderr.我使用了 .NET 类 Process 和 ProcessStartInfo 以及 stdin/stdout/stderr 的重定向。

    public ConsoleAccess()
    {
        if (!File.Exists(consoleAccessPath)) throw new FileNotFoundException(consoleAccessPath + " not found");

        myProcess = new Process();
        ProcessStartInfo myProcessStartInfo = new ProcessStartInfo(consoleAccessPath, ""); // even "2>&1" as argument does not work; my code still hangs
        myProcessStartInfo.CreateNoWindow = true; 
        myProcessStartInfo.UseShellExecute = false; 
        myProcessStartInfo.RedirectStandardOutput = true;
        myProcessStartInfo.RedirectStandardError = true;
        myProcessStartInfo.RedirectStandardInput = true;
        //myProcessStartInfo.ErrorDialog = true; // I tried, to no avail.
        myProcess.StartInfo = myProcessStartInfo;

        outputQueue = new ConcurrentQueue<string>(); // thread-safe queue
        errorQueue = new ConcurrentQueue<string>();

        myProcess.Start();
        myStandardOutput = myProcess.StandardOutput;
        myStandardError = myProcess.StandardError;
        myStandardInput = myProcess.StandardInput;

        stdOutPumper = new Thread(new ThreadStart(PumpStdOutLoop));
        stdOutPumper.Start();
        stdErrPumper = new Thread(new ThreadStart(PumpStdErrLoop));
        stdErrPumper.Start();

        string empty = getResponse(); // check for prompt
        string version = getVersion(); // one simple command
    }
    // [...]
    private void PumpStdErrLoop()
    {
        while (true)
        {
            string message = myStandardError.ReadLine();
            errorQueue.Enqueue(message);
        }
    }

    private void PumpStdOutLoop()
    {
        while (true)
        {
            bool done = false;
            string buffer = "";
            //int blocksize = 1024;
            string prompt = "Prompt> ";
            while (!done)
            {
                //char[] intermediaire = new char[blocksize];
                //int res = myStandardOutput.Read(intermediaire, 0, blocksize);
                //buffer += new string(intermediaire).Substring(0, res);
                byte b = (byte)myStandardOutput.Read(); // I go byte per byte, just in case the char[] above is the source of the problem. To no avail.
                buffer += (char)b;
                done = buffer.EndsWith(prompt);
            }
            buffer = buffer.Substring(0, buffer.Length - prompt.Length);
            outputQueue.Enqueue(buffer);
        }
    }

Since this program returns "Prompt> " (important : without "\\n" at the end) when it's waiting for commands, I can't use myProcess.BeginOutputReadLine();由于该程序在等待命令时返回“Prompt>”(重要的是:末尾没有“\\n”),因此我无法使用 myProcess.BeginOutputReadLine();

However, I have to use threads because I must listen stdout AND stderr at the same time.但是,我必须使用线程,因为我必须同时监听 stdout 和 stderr。

This is why I used threads and thread-safe queues for a class producer/consumer pattern.这就是我将线程和线程安全队列用于类生产者/消费者模式的原因。

"You can use asynchronous read operations to avoid these dependencies and their deadlock potential. Alternately, you can avoid the deadlock condition by creating two threads and reading the output of each stream on a separate thread." “您可以使用异步读取操作来避免这些依赖关系及其潜在的死锁。或者,您可以通过创建两个线程并在单独的线程上读取每个流的输出来避免死锁情况。” source: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput%28v=vs.100%29.aspx来源: http : //msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput%28v=vs.100%29.aspx

With this design, all sequences like * cmd -> result with no err (something on stdout, nothing on stderr) * cmd -> error (something on stderr, nothing on stdout) works as expected.通过这种设计,像 * cmd -> result 没有错误(stdout 上的某些内容,stderr 上没有任何内容)* cmd -> error(stderr 上的某些内容,stdout 上没有任何内容)之类的所有序列都按预期工作。 no problem.没问题。

  • cmd -> result with warning (something on both stderr and stdout) should work (I'm trying to reproduce this scenario) cmd -> 带有警告的结果(stderr 和 stdout 上的某些内容)应该可以工作(我正在尝试重现这种情况)

however, for one command in particular -- a command that prompts for a password during its execution -- does not work:但是,特别是对于一个命令——一个在执行过程中提示输入密码的命令——不起作用:

  • main thread principal loops forever on if (errorQueue.Count == 0 && outputQueue.Count == 0) { System.Threading.Thread.Sleep(500); }主线程主体在if (errorQueue.Count == 0 && outputQueue.Count == 0) { System.Threading.Thread.Sleep(500); }上永远循环if (errorQueue.Count == 0 && outputQueue.Count == 0) { System.Threading.Thread.Sleep(500); }
  • thread pumping stdout waits forever on byte b = (byte)myStandardOutput.Read();线程泵标准输出永远等待byte b = (byte)myStandardOutput.Read();
  • thread pumping stdout waits a line forever on string message = myStandardError.ReadLine();线程泵标准输出在string message = myStandardError.ReadLine();上永远等待一行

What I don't get is why byte b = (byte)myStandardOutput.Read();我不明白的是为什么byte b = (byte)myStandardOutput.Read(); does not pump the message "password:".不会发送消息“密码:”。 Nothing happens.什么都没发生。 I never get the first 'p'.我从来没有得到第一个'p'。

I feel I hit a deadlock scenario, but I do not understand why.我觉得我陷入了僵局,但我不明白为什么。

What's wrong?怎么了?

(I don't think it is very relevant but I tried the above on .NET 4.0 with MS Visual Studio 2010 on Windows 7 32-bit.) (我认为这不是很相关,但我在 .NET 4.0 和 Windows 7 32 位上的 MS Visual Studio 2010 上尝试了上述操作。)

This is a very common failure mode for these kind of interactive console mode programs.对于此类交互式控制台模式程序,这是一种非常常见的故障模式。 The C runtime library automatically switches the stderr and stdout streams to buffered mode when it detects that output is being redirected.当 C 运行时库检测到输出被重定向时,它会自动将 stderr 和 stdout 流切换到缓冲模式。 Important to improve throughput.对提高吞吐量很重要。 So output goes into that buffer instead of getting directly written to the console.因此输出进入该缓冲区而不是直接写入控制台。 Getting your program to see the output requires the buffer to be flushed.让您的程序看到输出需要刷新缓冲区。

There are three scenarios where the buffer gets flushed.缓冲区被刷新的三种情况。 A flush occurs when the buffer is full, typically around 2 kilobytes.当缓冲区已满时会发生刷新,通常约为 2 KB。 Or when the program writes a line terminator (\\n).或者当程序写入行终止符 (\\n) 时。 Or when the program explicitly calls fflush().或者当程序显式调用 fflush() 时。 The first two scenarios do not occur, not enough output and the program isn't using \\n.前两种情况不会发生,没有足够的输出并且程序没有使用 \\n。 Which points at the problem, the original programmer forgot to call fflush().问题就在于,原来的程序员忘记调用fflush()了。 Forgetting this is very common, the programmer simply never intended the program to be used other than in an interactive way.忘记这一点是常见的,程序员根本不打算以交互方式以外的方式使用该程序。

Nothing can do about it, you'll need to ask the owner or author of the program to add fflush().对此无能为力,您需要要求程序的所有者或作者添加 fflush()。 Maybe you can limp along by just assuming that the prompt is being written.也许您可以通过假设正在编写提示来跛行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM