StandardOutput.ReadToEnd()挂起

时间:2011-08-23 11:19:34

标签: c# stream hang redirectstandardoutput

我有一个经常使用外部程序并读取其输出的程序。 它使用你通常的进程重定向输出很好地工作,但是当我尝试读取它时,由于某种原因,一个特定的参数会挂起,没有错误消息 - 没有例外,当它到达该行时它只是'停止'。 我当然使用集中式函数来调用和读取程序的输出,这就是:

public string ADBShell(string adbInput)
{
    try
    {
        //Create Empty values
        string result = string.Empty;
        string error = string.Empty;
        string output = string.Empty;
        System.Diagnostics.ProcessStartInfo procStartInfo 
            = new System.Diagnostics.ProcessStartInfo(toolPath + "adb.exe");

        procStartInfo.Arguments = adbInput;
        procStartInfo.RedirectStandardOutput = true;
        procStartInfo.RedirectStandardError = true;
        procStartInfo.UseShellExecute = false;
        procStartInfo.CreateNoWindow = true;
        procStartInfo.WorkingDirectory = toolPath;
        System.Diagnostics.Process proc = new System.Diagnostics.Process();
        proc.StartInfo = procStartInfo;
        proc.Start();
        // Get the output into a string
        proc.WaitForExit();
        result = proc.StandardOutput.ReadToEnd();
        error = proc.StandardError.ReadToEnd();  //Some ADB outputs use this
        if (result.Length > 1)
        {
            output += result;
        }
        if (error.Length > 1)
        {
            output += error;
        }
        Return output;
    }
    catch (Exception objException)
    {
        throw objException;
    }
}

挂起的行是result = proc.StandardOutput.ReadToEnd();,但不是每次都发送一个特定的参数(“start-server”)。所有其他参数都可以正常工作 - 它读取值并返回它。 挂起的方式也很奇怪。它不会冻结或给出错误或任何东西,它只是停止处理。好像它是一个'return'命令,除了它甚至没有返回到调用函数,它只是停止所有接口仍然启动并运行。 以前有人经历过吗?任何人都知道我应该尝试什么?我假设它在流本身内是意想不到的东西,但有没有办法可以处理/忽略它以便它无论如何都能读取它?

9 个答案:

答案 0 :(得分:51)

使用BeginOutputReadLine()提出的解决方案是一种很好的方法,但在这种情况下,它不适用,因为进程(当然使用WaitForExit())退出的时间比异步输出完全完成。

因此,我尝试同步实现它,并发现解决方案是使用Peek()类中的StreamReader方法。我添加了对Peek() > -1的检查以确保它不是在MSDN article描述的流的结尾,并且最终它可以工作并停止挂起!

以下是代码:

var process = new Process();
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.WorkingDirectory = @"C:\test\";
process.StartInfo.FileName = "test.exe";
process.StartInfo.Arguments = "your arguments here";

process.Start();
var output = new List<string>();

while (process.StandardOutput.Peek() > -1)
{
    output.Add(process.StandardOutput.ReadLine());
}

while (process.StandardError.Peek() > -1)
{
    output.Add(process.StandardError.ReadLine());
}
process.WaitForExit();

答案 1 :(得分:17)

问题是您在ReadToEndStandardOutput流上使用同步StandardError方法。这可能会导致您遇到的潜在僵局。这甚至在MSDN中有所描述。那里描述了解决方案。基本上,它是:使用异步版本BeginOutputReadLine来读取StandardOutput流的数据:

p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();

使用BeginOutputReadLine实现异步读取请参阅ProcessStartInfo hanging on "WaitForExit"? Why?

答案 2 :(得分:5)

如下:

process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();

process.OutputDataReceived += (sender, args) =>
                               {
                                    var outputData = args.Data;
                                    // ...
                                };
process.ErrorDataReceived += (sender, args) =>
                            {
                                var errorData = args.Data;
                                // ...
                            };
process.WaitForExit();

答案 3 :(得分:3)

我遇到了同样的死锁问题。这段代码片段对我有用。

        ProcessStartInfo startInfo = new ProcessStartInfo("cmd")
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            CreateNoWindow = true
        };

        Process process = new Process();
        process.StartInfo = startInfo;
        process.Start();
        process.StandardInput.WriteLine("echo hi");
        process.StandardInput.WriteLine("exit");
        var output = process.StandardOutput.ReadToEnd();
        process.Dispose();

答案 4 :(得分:2)

我遇到了与错误相同的问题。

根据您对Daniel Hilgarth的回复,我甚至没有尝试使用这些代码,但我认为它们对我有用。

因为我希望能够做一些更好的输出,但最终我决定在两个输出都在后台线程中完成。

public static class RunCommands
{
    #region Outputs Property

    private static object _outputsLockObject;
    private static object OutputsLockObject
    { 
        get
        {
            if (_outputsLockObject == null)
                Interlocked.CompareExchange(ref _outputsLockObject, new object(), null);
            return _outputsLockObject;
        }
    }

    private static Dictionary<object, CommandOutput> _outputs;
    private static Dictionary<object, CommandOutput> Outputs
    {
        get
        {
            if (_outputs != null)
                return _outputs;

            lock (OutputsLockObject)
            {
                _outputs = new Dictionary<object, CommandOutput>();
            }
            return _outputs;
        }
    }

    #endregion

    public static string GetCommandOutputSimple(ProcessStartInfo info, bool returnErrorIfPopulated = true)
    {
        // Redirect the output stream of the child process.
        info.UseShellExecute = false;
        info.CreateNoWindow = true;
        info.RedirectStandardOutput = true;
        info.RedirectStandardError = true;
        var process = new Process();
        process.StartInfo = info;
        process.ErrorDataReceived += ErrorDataHandler;
        process.OutputDataReceived += OutputDataHandler;

        var output = new CommandOutput();
        Outputs.Add(process, output);

        process.Start();

        process.BeginErrorReadLine();
        process.BeginOutputReadLine();

        // Wait for the process to finish reading from error and output before it is finished
        process.WaitForExit();

        Outputs.Remove(process);

        if (returnErrorIfPopulated && (!String.IsNullOrWhiteSpace(output.Error)))
        {
            return output.Error.TrimEnd('\n');
        }

        return output.Output.TrimEnd('\n');
    }

    private static void ErrorDataHandler(object sendingProcess, DataReceivedEventArgs errLine)
    {
        if (errLine.Data == null)
            return;

        if (!Outputs.ContainsKey(sendingProcess))
            return;

        var commandOutput = Outputs[sendingProcess];

        commandOutput.Error = commandOutput.Error + errLine.Data + "\n";
    }

    private static void OutputDataHandler(object sendingProcess, DataReceivedEventArgs outputLine)
    {
        if (outputLine.Data == null)
            return;

        if (!Outputs.ContainsKey(sendingProcess))
            return;

        var commandOutput = Outputs[sendingProcess];

        commandOutput.Output = commandOutput.Output + outputLine.Data + "\n";
    }
}
public class CommandOutput
{
    public string Error { get; set; }
    public string Output { get; set; }

    public CommandOutput()
    {
        Error = "";
        Output = "";
    }
}

这对我有用,并且允许我不必使用超时读取。

答案 5 :(得分:2)

优雅且适合我的东西是:

Process nslookup = new Process()
{
   StartInfo = new ProcessStartInfo("nslookup")
   {
      RedirectStandardInput = true,
      RedirectStandardOutput = true,
      UseShellExecute = false,
      CreateNoWindow = true,
      WindowStyle = ProcessWindowStyle.Hidden
   }
};

nslookup.Start();
nslookup.StandardInput.WriteLine("set type=srv");
nslookup.StandardInput.WriteLine("_ldap._tcp.domain.local"); 

nslookup.StandardInput.Flush();
nslookup.StandardInput.Close();

string output = nslookup.StandardOutput.ReadToEnd();

nslookup.WaitForExit();
nslookup.Close();

这个答案我找到了here,其诀窍是在标准输入上使用Flush()Close()

答案 6 :(得分:2)

接受的答案解决方案对我不起作用。我不得不使用任务以避免死锁:

//Code to start process here

String outputResult = GetStreamOutput(process.StandardOutput);
String errorResult = GetStreamOutput(process.StandardError);

process.WaitForExit();

使用GetStreamOutput功能如下:

private string GetStreamOutput(StreamReader stream)
{
   //Read output in separate task to avoid deadlocks
   var outputReadTask = Task.Run(() => stream.ReadToEnd());

   return outputReadTask.Result;
}

答案 7 :(得分:0)

以防万一有人在使用Windows窗体和TextBox(或RichTextBox)来显示错误和输出的过程中偶然发现这个问题,并将进程实时返回(因为它们被写入{ {1}} / process.StandardOutput)。

你需要使用process.StandardError / OutputDataReceived()来读取没有死锁的两个流,没有办法(据我所知)来避免死锁,否则,甚至Fedor的回答,现在持有&#34;答案&#34;标签和最喜欢的最新版本,对我来说不起作用。

但是,当您使用RichTextBox(或TextBox)输出数据时,您遇到的另一个问题是如何实时将数据写入文本框(一旦到达)。您可以访问其中一个后台线程ErrorDataReceived() / OutputDataReceived()内的数据,并且只能从主线程中ErrorDataReceived()

我首先尝试做的是从后台线程调用AppendText()然后在process.Start() / BeginInvoke() => AppendText()个线程中调用OutputDataReceived(),而主线程为ErrorDataReceived()

然而,这导致我的形式冻结并最终悬挂永恒。经过几天的尝试后,我得到了下面的解决方案,这看起来效果很好。

简而言之,您需要将消息添加到process.WaitForExit() / OutputDataReceived()个线程内的并发集合中,而主线程应该不断尝试从该集合中提取消息并将它们附加到文本框中:< / p>

ErrorDataReceived()

这种方法的唯一缺点是,当流程开始在 ProcessStartInfo startInfo = new ProcessStartInfo(File, mysqldumpCommand); process.StartInfo.FileName = File; process.StartInfo.Arguments = mysqldumpCommand; process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.StartInfo.RedirectStandardInput = false; process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.StandardErrorEncoding = Encoding.UTF8; process.StartInfo.StandardOutputEncoding = Encoding.UTF8; process.EnableRaisingEvents = true; ConcurrentQueue<string> messages = new ConcurrentQueue<string>(); process.ErrorDataReceived += (object se, DataReceivedEventArgs ar) => { string data = ar.Data; if (!string.IsNullOrWhiteSpace(data)) messages.Enqueue(data); }; process.OutputDataReceived += (object se, DataReceivedEventArgs ar) => { string data = ar.Data; if (!string.IsNullOrWhiteSpace(data)) messages.Enqueue(data); }; process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); while (!process.HasExited) { string data = null; if (messages.TryDequeue(out data)) UpdateOutputText(data, tbOutput); Thread.Sleep(5); } process.WaitForExit(); process.Start() / process.BeginErrorReadLine()之间写入时,您可以在非常罕见的情况下丢失消息,只需保留心里。避免这种情况的唯一方法是读取完整的流,并且(或)只有在流程结束时才能访问它们。

答案 8 :(得分:-1)

第一

     // Start the child process.
     Process p = new Process();
     // Redirect the output stream of the child process.
     p.StartInfo.UseShellExecute = false;
     p.StartInfo.RedirectStandardOutput = true;
     p.StartInfo.FileName = "Write500Lines.exe";
     p.Start();
     // Do not wait for the child process to exit before
     // reading to the end of its redirected stream.
     // p.WaitForExit();
     // Read the output stream first and then wait.
     string output = p.StandardOutput.ReadToEnd();
     p.WaitForExit();

第二

 // Do not perform a synchronous read to the end of both 
 // redirected streams.
 // string output = p.StandardOutput.ReadToEnd();
 // string error = p.StandardError.ReadToEnd();
 // p.WaitForExit();
 // Use asynchronous read operations on at least one of the streams.
 p.BeginOutputReadLine();
 string error = p.StandardError.ReadToEnd();
 p.WaitForExit();

这是MSDN