Diagnostic.Process合并StandardOutput和StandardErrorOutput

时间:2015-04-23 08:39:45

标签: c# .net

我正在使用Process类从.NET程序运行cmd.exe并传入各种命令来执行程序,例如ipconfig,netstat等。问题是有时这些程序输出Error文本,它被发送到StandardError,但是没有办法知道在标准输出期间究竟在哪里确实打印了错误。我得到的只是错误信息。是否可以将StandardOutput和StandardError组合成一个同步流,以便按顺序获取所有数据?如果没有,那么如何组织来自两个不同流的输出以使其按正确顺序排列?

1 个答案:

答案 0 :(得分:2)

好。我猜你真的不能指望STDOUT和STDERR按照它们输出的确切顺序排列。这是因为在系统级别,STDOUT和STDERR分别有自己的缓冲区。你可以想象这样的情况 -

(1)当'command'首先写入STDOUT时,缓冲区可能不会立即刷新。

(2)然后'command'写入STDERR,但是这次STDERR的缓冲区可能会被刷新。

在这种情况下,即使(1)首先出现,但我们最终获得的是(2)首先出现。

希望以下代码段可以提供帮助 -

(改进代码)

    class OutputContext
    {
        public const int BufferSize = 1024;

        public bool IsEof { get; set; }
        public bool IsWaiting { get; set; }
        public byte[] Buffer { get; set; }
        public StreamReader Reader { get; set; }
        public object Tag { get; set; }

        public OutputContext(StreamReader r, object tag)
        {
            IsEof = false;
            IsWaiting = false;
            Buffer = new byte[BufferSize];
            Reader = r;
            Tag = tag;
        }
    }

    Process proc;

    void Callback(IAsyncResult ar)
    {
        lock (ar.AsyncState)
        {
            OutputContext ctx = ar.AsyncState as OutputContext;
            int c = ctx.Reader.BaseStream.EndRead(ar);
            ctx.IsWaiting = false;

            if (c == 0)
            {
                ctx.IsEof = true;
                return;
            }

            string content = Encoding.UTF8.GetString(ctx.Buffer, 0, c);
            Console.Write(content);
            Console.Out.Flush();

        }
    }

    void RedirectOutput(OutputContext ctx)
    {
        lock (ctx)
        {
            if (ctx.IsEof)
            {
                return;
            }

            if (ctx.IsWaiting)
            {
                return;
            }

            ctx.IsWaiting = true;
            IAsyncResult ar = ctx.Reader.BaseStream.BeginRead(ctx.Buffer, 0, 
                OutputContext.BufferSize, Callback, ctx);
        }
    }

    void Run(string yourprog, string yourargs)
    {
        // If this is a GUI app, this shall not be run on the UI thread.
        // Spin a new thread to handle it and wait for the thread to complete.
        // And you can always accept the input as long as the UI thread is not
        // blocked, and redirect the input to the target proc's stdin asycly.

        proc = new Process();
        proc.StartInfo.CreateNoWindow = true;
        proc.StartInfo.FileName = yourprog;
        proc.StartInfo.Arguments = yourargs;
        proc.StartInfo.UseShellExecute = false;

        proc.StartInfo.RedirectStandardOutput = true;
        proc.StartInfo.RedirectStandardError = true;
        proc.StartInfo.RedirectStandardInput = true;

        proc.Start();

        OutputContext stdoutCtx = new OutputContext(proc.StandardOutput, "STDOUT");
        OutputContext stderrCtx = new OutputContext(proc.StandardError, "STDERR");

        while (!stdoutCtx.IsEof && !stderrCtx.IsEof)
        {
            RedirectOutput(stdoutCtx);
            RedirectOutput(stderrCtx);
        }

        proc.WaitForExit();
    }
}