C#-StandardOutput.ReadToEnd()挂起

这个问题已经在这里有了答案:

  • ProcessStartInfo挂在“ WaitForExit”上吗? 为什么? 21个答案

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

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();,但同样,并非每次,仅在发送特定参数(“启动服务器”)时才会发生。 其他所有参数都可以正常工作-读取并返回值。它的挂起方式也很奇怪。 它不会冻结或发出任何错误,它只是停止处理。 就像是一个“返回”命令一样,除了它甚至没有返回到调用函数之外,它只是在界面仍处于运行状态时停止所有操作。有人经历过吗? 有人知道我应该尝试什么吗? 我假设它在流本身中是意外的,但是有没有办法我可以处理/忽略它以便它仍然可以读取它?

Elad Avron asked 2020-07-09T22:09:38Z
9个解决方案
52 votes

使用Peek()提出的解决方案是一个好方法,但是在这种情况下不适用,因为进程(一定使用StreamReader)的退出要比异步输出完全完成早。

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

这是代码:

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();
Fedor answered 2020-07-09T22:10:18Z
17 votes

问题是您在StandardOutputStandardError流上都使用了同步BeginOutputReadLine方法。 这可能会导致您遇到潜在的死锁。 MSDN中甚至对此进行了描述。 解决方案在此处描述。 基本上是:使用异步版本BeginOutputReadLine读取StandardOutput流的数据:

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

使用BeginOutputReadLine进行异步读取的实现,请参见挂在“ WaitForExit”上的ProcessStartInfo? 为什么?

Daniel Hilgarth answered 2020-07-09T22:10:43Z
5 votes

怎么样呢:

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

process.OutputDataReceived += (sender, args) =>
                               {
                                    var outputData = args.Data;
                                    // ...
                                };
process.ErrorDataReceived += (sender, args) =>
                            {
                                var errorData = args.Data;
                                // ...
                            };
process.WaitForExit();
Cesario answered 2020-07-09T22:11:03Z
3 votes

我有同样的僵局问题。 此代码段对我有用。

        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();
gwasterisk answered 2020-07-09T22:11:23Z
2 votes

我遇到了同样的问题,即错误刚刚悬而未决。

根据您对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 = "";
    }
}

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

Matt Vukomanovic answered 2020-07-09T22:11:56Z
2 votes

对我来说优雅而有用的东西是:

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();

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

dragan.stepanovic answered 2020-07-09T22:12:21Z
2 votes

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

//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;
}
Meta-Knight answered 2020-07-09T22:12:45Z
0 votes

以防万一有人偶然使用Windows Forms和process.Start()(或process.BeginErrorReadLine())显示错误并输出过程实时返回时偶然发现此问题(将它们写入process.BeginOutputReadLine()/ErrorDataReceived())。

您需要使用process.Start()/process.BeginErrorReadLine()来读取两个没有死锁的流,否则(据我所知)没有其他方法可以避免死锁,即使是Fedor的答案,该答案现在带有“ Answer”标签,并且最喜欢 约会,对我没有帮助。

但是,当您使用RichTextBox(或TextBox)输出数据时,遇到的另一个问题是如何实时地(一旦到达)将数据实际写入文本框。 您可以在其中一个后台线程process.Start()/process.BeginErrorReadLine()中访问数据,而从主线程只能访问process.BeginOutputReadLine()

我首先尝试做的是从后台线程调用process.Start(),然后在process.BeginOutputReadLine()/ErrorDataReceived()线程中调用process.BeginErrorReadLine(),而主线程为process.WaitForExit()

但是,这导致我的表格冻结,并最终永久吊死。 经过几天的尝试,我最终得到了下面的解决方案,这似乎工作得很好。

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

            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()/process.BeginOutputReadLine()之间开始编写消息时,请记住这一点。 避免这种情况的唯一方法是读取完整的流,并(或)仅在过程完成后才能访问它们。

cubrman answered 2020-07-09T22:13:34Z
-1 votes

第一

     // 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

Jing answered 2020-07-09T22:14:02Z
translate from https://stackoverflow.com:/questions/7160187/standardoutput-readtoend-hangs