我有以下代码:

info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args));
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd()); //need the StandardOutput contents

我知道我正在启动的进程的输出大约有7MB长。在Windows控制台中运行它可以正常工作。不幸的是,从编程的角度来看,它会无限期地挂在WaitForExit上。还要注意,对于较小的输出(比如3KB),这段代码不会挂起。

ProcessStartInfo中的内部StandardOutput是否可能不能缓冲7MB?如果是,我该怎么办?如果不是,我做错了什么?


当前回答

https://stackoverflow.com/a/17600012/4151626的信用归于EM0

我的应用程序的其他解决方案(包括EM0)仍然处于死锁状态,这是由于内部超时以及派生应用程序同时使用StandardOutput和standderror。以下是对我有效的方法:

Process p = new Process()
{
  StartInfo = new ProcessStartInfo()
  {
    FileName = exe,
    Arguments = args,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true
  }
};
p.Start();

string cv_error = null;
Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); });
et.Start();

string cv_out = null;
Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); });
ot.Start();

p.WaitForExit();
ot.Join();
et.Join();

编辑:添加初始化StartInfo代码示例

其他回答

简介

目前接受的答案不工作(抛出异常),有太多的变通方法,但没有完整的代码。这显然是在浪费很多人的时间,因为这是一个很受欢迎的问题。

结合Mark Byers的回答和Karol Tyl的回答,我根据我想如何使用流程编写了完整的代码。Start方法。

使用

我用它来创建git命令的进度对话框。我是这样使用它的:

    private bool Run(string fullCommand)
    {
        Error = "";
        int timeout = 5000;

        var result = ProcessNoBS.Start(
            filename: @"C:\Program Files\Git\cmd\git.exe",
            arguments: fullCommand,
            timeoutInMs: timeout,
            workingDir: @"C:\test");

        if (result.hasTimedOut)
        {
            Error = String.Format("Timeout ({0} sec)", timeout/1000);
            return false;
        }

        if (result.ExitCode != 0)
        {
            Error = (String.IsNullOrWhiteSpace(result.stderr)) 
                ? result.stdout : result.stderr;
            return false;
        }

        return true;
    }

理论上,您还可以结合stdout和stderr,但我还没有对此进行测试。

Code

public struct ProcessResult
{
    public string stdout;
    public string stderr;
    public bool hasTimedOut;
    private int? exitCode;

    public ProcessResult(bool hasTimedOut = true)
    {
        this.hasTimedOut = hasTimedOut;
        stdout = null;
        stderr = null;
        exitCode = null;
    }

    public int ExitCode
    {
        get 
        {
            if (hasTimedOut)
                throw new InvalidOperationException(
                    "There was no exit code - process has timed out.");

            return (int)exitCode;
        }
        set
        {
            exitCode = value;
        }
    }
}

public class ProcessNoBS
{
    public static ProcessResult Start(string filename, string arguments,
        string workingDir = null, int timeoutInMs = 5000,
        bool combineStdoutAndStderr = false)
    {
        using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
        using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
        {
            using (var process = new Process())
            {
                var info = new ProcessStartInfo();

                info.CreateNoWindow = true;
                info.FileName = filename;
                info.Arguments = arguments;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                if (workingDir != null)
                    info.WorkingDirectory = workingDir;

                process.StartInfo = info;

                StringBuilder stdout = new StringBuilder();
                StringBuilder stderr = combineStdoutAndStderr
                    ? stdout : new StringBuilder();

                var result = new ProcessResult();

                try
                {
                    process.OutputDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            outputWaitHandle.Set();
                        else
                            stdout.AppendLine(e.Data);
                    };
                    process.ErrorDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            errorWaitHandle.Set();
                        else
                            stderr.AppendLine(e.Data);
                    };

                    process.Start();

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

                    if (process.WaitForExit(timeoutInMs))
                        result.ExitCode = process.ExitCode;
                    // else process has timed out 
                    // but that's already default ProcessResult

                    result.stdout = stdout.ToString();
                    if (combineStdoutAndStderr)
                        result.stderr = null;
                    else
                        result.stderr = stderr.ToString();

                    return result;
                }
                finally
                {
                    outputWaitHandle.WaitOne(timeoutInMs);
                    errorWaitHandle.WaitOne(timeoutInMs);
                }
            }
        }
    }
}

我最终使用的解决方案来避免所有的复杂性:

var outputFile = Path.GetTempFileName();
info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args) + " > " + outputFile + " 2>&1");
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(File.ReadAllText(outputFile)); //need the StandardOutput contents

所以我创建了一个临时文件,通过使用> outputfile > 2>&1将输出和错误重定向到它,然后在进程完成后读取文件。

其他解决方案适用于希望对输出执行其他操作的场景,但对于简单的操作,这可以避免很多复杂性。

在我的情况下,我有一个错误,所以我只是徒劳地等待一个正常的输出。

我改变了顺序:

string result = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

:

string error = process.StandardError.ReadToEnd();

if (string.IsNullOrEmpty(error))
    string result = process.StandardOutput.ReadToEnd();

我是这样解决的:

            Process proc = new Process();
            proc.StartInfo.FileName = batchFile;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.RedirectStandardError = true;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;      
            proc.Start();
            StreamWriter streamWriter = proc.StandardInput;
            StreamReader outputReader = proc.StandardOutput;
            StreamReader errorReader = proc.StandardError;
            while (!outputReader.EndOfStream)
            {
                string text = outputReader.ReadLine();                    
                streamWriter.WriteLine(text);
            }

            while (!errorReader.EndOfStream)
            {                   
                string text = errorReader.ReadLine();
                streamWriter.WriteLine(text);
            }

            streamWriter.Close();
            proc.WaitForExit();

我重定向输入,输出和错误,并处理从输出和错误流读取。 此解决方案适用于SDK 7- 8.1,适用于Windows 7和Windows 8

在阅读了这里所有的帖子之后,我确定了Marko avlijaovic的统一解决方案。 然而,它并没有解决我所有的问题。

在我们的环境中,我们有一个Windows服务,计划运行数百个不同的.bat .cmd .exe,…等等,这些文件积累了多年,由许多不同的人以不同的风格编写。我们无法控制程序和脚本的编写,我们只负责调度、运行和成功/失败的报告。

所以我尝试了这里所有的建议,并取得了不同程度的成功。Marko的回答几乎是完美的,但是当作为服务运行时,它并不总是捕获标准输出。我一直没搞清楚为什么不行。

我们发现在所有情况下都有效的唯一解决方案是:http://csharptest.net/319/using-the-processrunner-class/index.html