C# Process.StandardOutput.Read无法可靠地获取数据,Process.StandardInput.WriteLine无法刷新其基流;缓冲区内容
我正在尝试制作一个简单的应用程序,它与控制台程序进行通信,控制台程序的操作方式与Windows经典cmd.exe类似 预计执行顺序如下:C# Process.StandardOutput.Read无法可靠地获取数据,Process.StandardInput.WriteLine无法刷新其基流;缓冲区内容,c#,.net,desktop-application,C#,.net,Desktop Application,我正在尝试制作一个简单的应用程序,它与控制台程序进行通信,控制台程序的操作方式与Windows经典cmd.exe类似 预计执行顺序如下: 设置后台工作程序并运行它以读取输出 运行该进程并等待片刻 将命令馈送到Process.StandardInput 等几秒钟 退出后台线程并终止进程 但发生的事情并不像预期的那样,因为: 并非cmd.exe的所有输出都被读取。即使排除写入StandardInput的步骤 即使字符串的AutoFlush=true,也没有从StandardInput传递给命令 我还
BackgroundWorker _outputWorker;
Process _process;
StreamWriter _inputWriter;
TextReader _outputReader;
void Main()
{
_outputWorker = new BackgroundWorker { WorkerSupportsCancellation = true };
_outputWorker.DoWork += OnOutputWorkerDoWork;
_outputWorker.RunWorkerAsync();
_process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = string.Empty,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Directory.GetCurrentDirectory(),
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
Console.WriteLine("Starting...");
if (!_process.Start()) return;
_inputWriter = _process.StandardInput;
_inputWriter.AutoFlush = true; // does nothing
_outputReader = TextReader.Synchronized(_process.StandardOutput);
// You can exclude this step too and still not get the expected output
Thread.Sleep(500);
_inputWriter.WriteLine("dir");
_inputWriter.Flush(); // does nothing, private field carpos = 0
_inputWriter.BaseStream.Flush(); // does nothing, private field carpos = 5 which is equal to length of "dir" command + 2 characters (NewLine \r\n)
//_inputWriter.WriteLine("dir".PadLeft(4096)); // does nothing
// also closing the stream does nothing and does something that I can't afford which is closing the exe
// _inputWriter.Close();
//
_process.WaitForExit(5000);
_outputWorker.CancelAsync();
_process.Kill();
Console.WriteLine("Done");
}
void OnOutput(string data)
{
// never mind thread safety for now. It's just a single line static call
Console.WriteLine(data);
}
void OnOutputWorkerDoWork(object sender, DoWorkEventArgs e)
{
const int BUFFER_SIZE = 4096;
StringBuilder builder = new StringBuilder(BUFFER_SIZE);
while (!_outputWorker.CancellationPending)
{
/*
* It'll keep on running untill it's canceled to reduce thread costs
* because the program will run different executables sequentially which
* all are similar to cmd.exe.
*/
try
{
// Simplified version without locking
if (_outputReader == null) continue;
TextReader reader = _outputReader;
if (reader.Peek() < 1) continue;
char[] buffer = new char[BUFFER_SIZE];
do
{
int count = reader.Read(buffer, 0, buffer.Length);
if (count > 0) builder.Append(buffer, 0, count);
}
while (reader.Peek() > 0);
}
catch (Exception ex)
{
// handle exception in debug mode
Console.WriteLine(ex.Message); // no exception generated!
continue;
}
if (builder.Length == 0) continue;
OnOutput(builder.ToString());
builder.Length = 0;
}
if (!IsWaitable(_process)) return;
try
{
if (_outputReader == null) return;
TextReader reader = _outputReader;
if (reader.Peek() < 1) return;
char[] buffer = new char[BUFFER_SIZE];
do
{
int count = reader.Read(buffer, 0, buffer.Length);
if (count > 0) builder.Append(buffer, 0, count);
}
while (reader.Peek() > 0);
}
catch (Exception ex)
{
// handle exception in debug mode
Console.WriteLine(ex.Message); // no exception generated!
return;
}
if (builder.Length > 0) OnOutput(builder.ToString());
}
bool IsWaitable(Process thisValue)
{
return thisValue != null && !thisValue.HasExited;
}
BackgroundWorker\u outputWorker;
过程(u过程),;
StreamWriter\u inputWriter;
文本阅读器(输出阅读器),;
void Main()
{
_outputWorker=new BackgroundWorker{WorkerSupportsCancellation=true};
_outputWorker.DoWork+=OnOutputWorkerDoWork;
_outputWorker.RunWorkerAsync();
_流程=新流程
{
EnableRaisingEvents=true,
StartInfo=新流程StartInfo
{
FileName=“cmd.exe”,
Arguments=string.Empty,
UseShellExecute=false,
CreateNoWindow=true,
WindowsStyle=ProcessWindowsStyle.Hidden,
WorkingDirectory=目录。GetCurrentDirectory(),
StandardOutputEncoding=Encoding.UTF8,
StandardErrorEncoding=Encoding.UTF8,
重定向标准输入=真,
重定向标准输出=真,
重定向标准错误=true
}
};
Console.WriteLine(“开始…”);
如果(!\u process.Start())返回;
_inputWriter=_process.StandardInput;
_inputWriter.AutoFlush=true;//不执行任何操作
_outputReader=TextReader.Synchronized(_process.StandardOutput);
//您也可以排除此步骤,但仍然无法获得预期的输出
睡眠(500);
_inputWriter.WriteLine(“目录”);
_inputWriter.Flush();//不执行任何操作,私有字段carpos=0
_inputWriter.BaseStream.Flush();//不执行任何操作,私有字段carpos=5,它等于“dir”命令的长度+2个字符(换行符\r\n)
//_inputWriter.WriteLine(“dir.PadLeft(4096));//不执行任何操作
//关闭流也没有任何作用,并且做了一些我负担不起的事情,那就是关闭exe
//_inputWriter.Close();
//
_进程:WaitForExit(5000);
_outputWorker.CancelAsync();
_process.Kill();
控制台。写入线(“完成”);
}
void OnOutput(字符串数据)
{
//现在不要管线程安全。这只是一个单线静态调用
控制台写入线(数据);
}
OutputWorkerDoWork无效(对象发送方,DoWorkerVentargs e)
{
const int BUFFER_SIZE=4096;
StringBuilder=新的StringBuilder(缓冲区大小);
while(!\u outputWorker.CancellationPending)
{
/*
*它将继续运行,直到被取消,以降低线程成本
*因为程序将按顺序运行不同的可执行文件
*它们都类似于cmd.exe。
*/
尝试
{
//简化版,无需锁定
如果(_outputReader==null)继续;
文本阅读器=_输出阅读器;
如果(reader.Peek()<1)继续;
char[]buffer=新字符[buffer_SIZE];
做
{
int count=reader.Read(buffer,0,buffer.Length);
如果(计数>0)builder.Append(缓冲区,0,计数);
}
while(reader.Peek()>0);
}
捕获(例外情况除外)
{
//在调试模式下处理异常
Console.WriteLine(ex.Message);//未生成异常!
继续;
}
如果(builder.Length==0)继续;
OnOutput(builder.ToString());
长度=0;
}
如果(!IsWaitable(_进程))返回;
尝试
{
if(_outputReader==null)返回;
文本阅读器=_输出阅读器;
if(reader.Peek()<1)返回;
char[]buffer=新字符[buffer_SIZE];
做
{
int count=reader.Read(buffer,0,buffer.Length);
如果(计数>0)builder.Append(缓冲区,0,计数);
}
while(reader.Peek()>0);
}
捕获(例外情况除外)
{
//在调试模式下处理异常
Console.WriteLine(ex.Message);//未生成异常!
返回;
}
如果(builder.Length>0)打开输出(builder.ToString());
}
bool是可等待的(处理此值)
{
返回thisValue!=null&!thisValue.HasExited;
}
我不能使用Process.BeginOutputReadLine,因为它等待在流中出现换行,而在最后一行输出中不会出现换行。有关详细信息,请参阅和
我明白了
Microsoft Windows[版本xxxxx]
(c) 版权线
除非包含换行符的进程有更多的输出,否则程序不会显示命令行提示符
兴趣点是:
1。为什么这段代码没有像它应该读取的那样读取所有输出到最后?
在尝试了许多事情之后,缓冲区似乎不再包含任何要读取的文本,并且流似乎丢失了可执行文件原始输出中的一些数据。有什么奇怪的
Process _process;
StreamWriter _inputWriter;
void Main()
{
_process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = string.Empty,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Directory.GetCurrentDirectory(),
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
_process.OutputDataReceived += (s, e) => // instead of using a background worker
{
if (e.Data == null) return;
Console.WriteLine(e.Data);
};
Console.WriteLine("Starting...");
if (!_process.Start()) return;
_process.BeginOutputReadLine(); // <- using BeginOutputReadLine
_inputWriter = _process.StandardInput;
_inputWriter.AutoFlush = true;
_inputWriter.WriteLine(); // <- my little trick here
// using LINQPad, replace it with Console.ReadLine();
string input = Util.ReadLine<string> ("Enter command:");
if (!string.IsNullOrEmpty(input)) _inputWriter.WriteLine(input);
_process.WaitForExit(5000);
_process.Kill();
Console.WriteLine("Done");
}
void OnOutput(string data)
{
Console.WriteLine(data);
}
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Class: AsyncStreamReader
**
** Purpose: For reading text from streams using a particular
** encoding in an asychronous manner used by the process class
**
**
===========================================================*/
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
namespace System.Diagnostics
{
/// <summary>
/// http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8@0/untmp/whidbey/REDBITS/ndp/fx/src/Services/Monitoring/system/Diagnosticts/AsyncStreamReader@cs/1/AsyncStreamReader@cs
/// </summary>
public sealed class AsyncStreamReader : DisposableBase, IDisposable
{
internal const int DEFAULT_BUFFER_SIZE = 4096; // Byte buffer size
private const int MIN_BUFFER_SIZE = 128;
private Decoder _decoder;
private byte[] _byteBuffer;
private char[] _charBuffer;
// Record the number of valid bytes in the byteBuffer, for a few checks.
// This is the maximum number of chars we can get from one call to
// ReadBuffer. Used so ReadBuffer can tell when to copy data into
// a user's char[] directly, instead of our internal char[].
private int _maxCharsPerBuffer;
// Store a backpointer to the process class, to check for user callbacks
private Process _process;
private StringBuilder _sb;
// Delegate to call user function.
private Action<string> _userCallBack;
// Internal Cancel operation
private bool _cancelOperation;
private ManualResetEvent _eofEvent;
public AsyncStreamReader(Process process, Stream stream, Action<string> callback, Encoding encoding)
: this(process, stream, callback, encoding, DEFAULT_BUFFER_SIZE)
{
}
// Creates a new AsyncStreamReader for the given stream. The
// character encoding is set by encoding and the buffer size,
// in number of 16-bit characters, is set by bufferSize.
public AsyncStreamReader(Process process, Stream stream, Action<string> callback, Encoding encoding, int bufferSize)
{
Debug.Assert(process != null && stream != null && encoding != null && callback != null, "Invalid arguments!");
Debug.Assert(stream.CanRead, "Stream must be readable!");
Debug.Assert(bufferSize > 0, "Invalid buffer size!");
Init(process, stream, callback, encoding, bufferSize);
}
private void Init(Process process, Stream stream, Action<string> callback, Encoding encoding, int bufferSize)
{
_process = process;
BaseStream = stream;
CurrentEncoding = encoding;
_userCallBack = callback;
_decoder = encoding.GetDecoder();
if (bufferSize < MIN_BUFFER_SIZE) bufferSize = MIN_BUFFER_SIZE;
_byteBuffer = new byte[bufferSize];
_maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
_charBuffer = new char[_maxCharsPerBuffer];
_sb = new StringBuilder(_charBuffer.Length);
_cancelOperation = false;
_eofEvent = new ManualResetEvent(false);
}
public void Close()
{
Dispose(true);
}
void IDisposable.Dispose()
{
Dispose(true);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (BaseStream != null)
{
BaseStream.Close();
BaseStream = null;
}
}
if (BaseStream != null)
{
BaseStream = null;
CurrentEncoding = null;
_decoder = null;
_byteBuffer = null;
_charBuffer = null;
}
if (_eofEvent != null)
{
_eofEvent.Close();
_eofEvent = null;
}
}
public Encoding CurrentEncoding { get; private set; }
public Stream BaseStream { get; private set; }
// User calls BeginRead to start the asynchronous read
public void BeginRead()
{
_cancelOperation = false;
BaseStream.BeginRead(_byteBuffer, 0, _byteBuffer.Length, ReadBuffer, null);
}
public void CancelOperation()
{
_cancelOperation = true;
}
// This is the async callback function. Only one thread could/should call this.
private void ReadBuffer(IAsyncResult ar)
{
if (_cancelOperation) return;
int byteLen;
try
{
byteLen = BaseStream.EndRead(ar);
}
catch (IOException)
{
// We should ideally consume errors from operations getting cancelled
// so that we don't crash the unsuspecting parent with an unhandled exc.
// This seems to come in 2 forms of exceptions (depending on platform and scenario),
// namely OperationCanceledException and IOException (for errorcode that we don't
// map explicitly).
byteLen = 0; // Treat this as EOF
}
catch (OperationCanceledException)
{
// We should consume any OperationCanceledException from child read here
// so that we don't crash the parent with an unhandled exc
byteLen = 0; // Treat this as EOF
}
if (byteLen == 0)
{
// We're at EOF, we won't call this function again from here on.
_eofEvent.Set();
}
else
{
int charLen = _decoder.GetChars(_byteBuffer, 0, byteLen, _charBuffer, 0);
if (charLen > 0)
{
_sb.Length = 0;
_sb.Append(_charBuffer, 0, charLen);
_userCallBack(_sb.ToString());
}
BaseStream.BeginRead(_byteBuffer, 0, _byteBuffer.Length, ReadBuffer, null);
}
}
// Wait until we hit EOF. This is called from Process.WaitForExit
// We will lose some information if we don't do this.
public void WaitUtilEof()
{
if (_eofEvent != null)
{
_eofEvent.WaitOne();
_eofEvent.Close();
_eofEvent = null;
}
}
}
}
Process _process;
StreamWriter _inputWriter;
AsyncStreamReader _output;
void Main()
{
_process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = string.Empty,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Directory.GetCurrentDirectory(),
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
Console.WriteLine("Starting...");
if (!_process.Start()) return;
BeginRead();
_inputWriter = _process.StandardInput;
_inputWriter.AutoFlush = true;
Thread.Sleep(500);
string input = Util.ReadLine<string>("Type a command:");
if (!string.IsNullOrEmpty(input)) _inputWriter.WriteLine(input);
_process.WaitForExit(5000);
CancelRead();
_process.Kill();
Console.WriteLine("Done");
}
void OnOutput(string data)
{
Console.Write(data);
}
void BeginRead()
{
if (_output == null) _output = new AsyncStreamReader(_process, _process.StandardOutput.BaseStream, OnOutput, _process.StandardOutput.CurrentEncoding);
_output.BeginRead();
}
void CancelRead()
{
_output.CancelOperation();
}