我正在开发一个 C# 应用程序,我需要启动一个外部控制台 程序来执行一些任务(提取文件)。我需要做的是重定向控制台程序的输出。类似 this one 的代码不起作用,因为它仅在控制台程序中写入新行时引发事件,但我使用的是“更新”控制台窗口中显示的内容,而不写入任何新行。每次更新控制台中的文本时如何引发事件?或者只是每隔 X 秒获取一次控制台程序的输出?提前致谢!
最佳答案
我遇到了一个与您描述的非常相似(可能完全相同)的问题:
- 我需要将控制台更新异步传送给我。
- 无论是否输入换行符,我都需要检测更新。
我最后做的是这样的:
- 开始调用
StandardOutput.BaseStream.BeginRead
的“无限”循环。 - 在
BeginRead
的回调中,检查EndRead
的返回值是否为0
;这意味着控制台进程已关闭其输出流(即永远不会再向标准输出写入任何内容)。 - 由于
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();
}
}
这样使用:
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();
关于C# - 实时控制台输出重定向,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4501511/