C# - 实时控制台输出重定向

15 c# console redirect

我正在开发一个C#应用程序,我需要启动一个外部控制台程序来执行一些任务(提取文件).我需要做的是重定向控制台程序的输出.像这样的代码不起作用,因为它仅在控制台程序中写入新行时引发事件,但是我使用的那个"更新"控制台窗口中显示的内容,而不写任何新行.每次更新控制台中的文本时,如何引发事件?或者每隔X秒获取一次控制台程序的输出?提前致谢!

Jon*_*Jon 13

我有一个与你描述的非常相似(可能是确切的)问题:

  1. 我需要将控制台更新异步传送给我.
  2. 无论是否输入了换行符,我都需要检测更新.

我最终做的是这样的:

  1. 开始一个"无休止"的呼叫循环StandardOutput.BaseStream.BeginRead.
  2. 在回调中BeginRead,检查返回值是否EndRead0; 这意味着控制台进程已关闭其输出流(即永远不会再向标准输出写入任何内容).
  3. 由于BeginRead强制使用常量长度缓冲区,请检查返回值EndRead是否等于缓冲区大小.这意味着可能有更多的输出等待读取,并且可能希望(或甚至必要)将该输出全部处理成一个整体.我做的是保持一个StringBuilder并附加到目前为止的输出读取.每当读取输出但其长度<缓冲区长度时,通知自己(我用事件做)有输出,将其内容发送StringBuilder给用户,然后清除它.

但是,就我而言,我只是在控制台的标准输出上写了更多内容.我不确定在你的情况下"更新"输出意味着什么.

更新:我刚刚意识到(不解释你在做什么一个很好的学习经验吗?),上面列出的逻辑有一个关闭的情况的一个错误:如果读取输出的长度BeginRead正好等于长度您缓冲区,然后这个逻辑将输出存储在StringBuilder和块中,同时试图查看是否有更多的输出要追加.只有当有更多输出可用时,"当前"输出才会发回给您,作为较大字符串的一部分.

显然,为了100%正确地做到这一点,需要一些防范这种方法(或者是一个巨大的缓冲加上对你的运气能力的信心)的方法.

更新2(代码):

免责声明:此代码不适合生产.这是我快速将概念验证解决方案拼凑在一起以完成需要完成的工作的结果.请不要在生产应用程序中使用它.如果这段代码导致你身上发生了可怕的事情,我会假装其他人写了它.

public class ConsoleInputReadEventArgs : EventArgs
{
    public ConsoleInputReadEventArgs(string input)
    {
        this.Input = input;
    }

    public string Input { get; private set; }
}

public interface IConsoleAutomator
{
    StreamWriter StandardInput { get; }

    event EventHandler<ConsoleInputReadEventArgs> StandardInputRead;
}

public abstract class ConsoleAutomatorBase : IConsoleAutomator
{
    protected readonly StringBuilder inputAccumulator = new StringBuilder();

    protected readonly byte[] buffer = new byte[256];

    protected volatile bool stopAutomation;

    public StreamWriter StandardInput { get; protected set; }

    protected StreamReader StandardOutput { get; set; }

    protected StreamReader StandardError { get; set; }

    public event EventHandler<ConsoleInputReadEventArgs> StandardInputRead;

    protected void BeginReadAsync()
    {
        if (!this.stopAutomation) {
            this.StandardOutput.BaseStream.BeginRead(this.buffer, 0, this.buffer.Length, this.ReadHappened, null);
        }
    }

    protected virtual void OnAutomationStopped()
    {
        this.stopAutomation = true;
        this.StandardOutput.DiscardBufferedData();
    }

    private void ReadHappened(IAsyncResult asyncResult)
    {
        var bytesRead = this.StandardOutput.BaseStream.EndRead(asyncResult);
        if (bytesRead == 0) {
            this.OnAutomationStopped();
            return;
        }

        var input = this.StandardOutput.CurrentEncoding.GetString(this.buffer, 0, bytesRead);
        this.inputAccumulator.Append(input);

        if (bytesRead < this.buffer.Length) {
            this.OnInputRead(this.inputAccumulator.ToString());
        }

        this.BeginReadAsync();
    }

    private void OnInputRead(string input)
    {
        var handler = this.StandardInputRead;
        if (handler == null) {
            return;
        }

        handler(this, new ConsoleInputReadEventArgs(input));
        this.inputAccumulator.Clear();
    }
}

public class ConsoleAutomator : ConsoleAutomatorBase, IConsoleAutomator
{
    public ConsoleAutomator(StreamWriter standardInput, StreamReader standardOutput)
    {
        this.StandardInput = standardInput;
        this.StandardOutput = standardOutput;
    }

    public void StartAutomate()
    {
        this.stopAutomation = false;
        this.BeginReadAsync();
    }

    public void StopAutomation()
    {
        this.OnAutomationStopped();
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样使用:

var processStartInfo = new ProcessStartInfo
    {
        FileName = "myprocess.exe",
        RedirectStandardInput = true,
        RedirectStandardOutput = true,
        UseShellExecute = false,
    };

var process = Process.Start(processStartInfo);
var automator = new ConsoleAutomator(process.StandardInput, process.StandardOutput);

// AutomatorStandardInputRead is your event handler
automator.StandardInputRead += AutomatorStandardInputRead;
automator.StartAutomate();

// do whatever you want while that process is running
process.WaitForExit();
automator.StandardInputRead -= AutomatorStandardInputRead;
process.Close();
Run Code Online (Sandbox Code Playgroud)


bri*_*ler 9

或者,根据保持理智的原则,您可以阅读文档并正确执行:

var startinfo = new ProcessStartInfo(@".\consoleapp.exe")
{
    CreateNoWindow = true,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
};

var process = new Process { StartInfo = startinfo };
process.Start();

var reader = process.StandardOutput;
while (!reader.EndOfStream)
{
    // the point is that the stream does not end until the process has 
    // finished all of its output.
    var nextLine = reader.ReadLine();
}

process.WaitForExit();
Run Code Online (Sandbox Code Playgroud)

  • 问题是 ReadLine()、ReadToend()、Peek() 以及您尝试使用锁的任何内容,并且不会按照文档中的描述返回。可能适用于文件流和内存流,但当您连接到控制台应用程序的流读取器时效果就不太好。如果您想从控制台捕获原始数据,最好获取所有换行符。 (3认同)