我有以下代码:

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?如果是,我该怎么办?如果不是,我做错了什么?


当前回答

我也有同样的问题,但原因不同。但是在Windows 8下会发生,而在Windows 7下不会。下面这行似乎是导致这个问题的原因。

pProcess.StartInfo.UseShellExecute = False

解决方案是不禁用UseShellExecute。我现在收到一个Shell弹出窗口,这是不需要的,但比程序等待什么特别的事情发生要好得多。所以我添加了如下的解决方法:

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

现在唯一让我困扰的是为什么在Windows 8下会出现这种情况。

其他回答

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

我改变了顺序:

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

:

string error = process.StandardError.ReadToEnd();

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

我知道这是晚餐,但是,在读完这一整页后,没有一个解决方案对我来说是有效的,尽管我没有尝试Muhammad Rehan,因为代码有点难理解,尽管我猜他是在正确的轨道上。当我说它不工作时,这并不完全正确,有时它会工作得很好,我猜这与EOF标记之前的输出长度有关。

不管怎样,对我来说有效的解决方案是使用不同的线程来读取StandardOutput和StandardError并写入消息。

        StreamWriter sw = null;
        var queue = new ConcurrentQueue<string>();

        var flushTask = new System.Timers.Timer(50);
        flushTask.Elapsed += (s, e) =>
        {
            while (!queue.IsEmpty)
            {
                string line = null;
                if (queue.TryDequeue(out line))
                    sw.WriteLine(line);
            }
            sw.FlushAsync();
        };
        flushTask.Start();

        using (var process = new Process())
        {
            try
            {
                process.StartInfo.FileName = @"...";
                process.StartInfo.Arguments = $"...";
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;

                process.Start();

                var outputRead = Task.Run(() =>
                {
                    while (!process.StandardOutput.EndOfStream)
                    {
                        queue.Enqueue(process.StandardOutput.ReadLine());
                    }
                });

                var errorRead = Task.Run(() =>
                {
                    while (!process.StandardError.EndOfStream)
                    {
                        queue.Enqueue(process.StandardError.ReadLine());
                    }
                });

                var timeout = new TimeSpan(hours: 0, minutes: 10, seconds: 0);

                if (Task.WaitAll(new[] { outputRead, errorRead }, timeout) &&
                    process.WaitForExit((int)timeout.TotalMilliseconds))
                {
                    if (process.ExitCode != 0)
                    {
                        throw new Exception($"Failed run... blah blah");
                    }
                }
                else
                {
                    throw new Exception($"process timed out after waiting {timeout}");
                }
            }
            catch (Exception e)
            {
                throw new Exception($"Failed to succesfully run the process.....", e);
            }
        }
    }

希望这能帮助那些认为这很难的人!

我们也有这个问题(或一个变体)。

试试下面的方法:

1)为p.WaitForExit(nnnn)添加超时;其中NNNN以毫秒为单位。

2)将ReadToEnd调用放在WaitForExit调用之前。这是我们看到MS推荐的。

Mark Byers的回答非常棒,但我想补充以下内容:

OutputDataReceived和ErrorDataReceived委托需要在outputWaitHandle和errorWaitHandle被释放之前被移除。如果进程在超过超时时间后继续输出数据,然后终止,则outputWaitHandle和errorWaitHandle变量将在被释放后被访问。

(仅供参考,我不得不加上这个警告作为回答,因为我不能评论他的帖子。)

上面的答案没有一个能起作用。

Rob解决方案挂起,“Mark Byers”解决方案获得已处理异常。(我尝试了其他答案的“解决方案”)。

所以我决定提出另一个解决方案:

public void GetProcessOutputWithTimeout(Process process, int timeoutSec, CancellationToken token, out string output, out int exitCode)
{
    string outputLocal = "";  int localExitCode = -1;
    var task = System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
        outputLocal = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        localExitCode = process.ExitCode;
    }, token);

    if (task.Wait(timeoutSec, token))
    {
        output = outputLocal;
        exitCode = localExitCode;
    }
    else
    {
        exitCode = -1;
        output = "";
    }
}

using (var process = new Process())
{
    process.StartInfo = ...;
    process.Start();
    string outputUnicode; int exitCode;
    GetProcessOutputWithTimeout(process, PROCESS_TIMEOUT, out outputUnicode, out exitCode);
}

这段代码经过调试,工作完美。