C# Process.StandardOutput.Read无法可靠地获取数据,Process.StandardInput.WriteLine无法刷新其基流;缓冲区内容

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传递给命令 我还

我正在尝试制作一个简单的应用程序,它与控制台程序进行通信,控制台程序的操作方式与Windows经典cmd.exe类似

预计执行顺序如下:

  • 设置后台工作程序并运行它以读取输出
  • 运行该进程并等待片刻
  • 将命令馈送到Process.StandardInput
  • 等几秒钟
  • 退出后台线程并终止进程
  • 但发生的事情并不像预期的那样,因为:

  • 并非cmd.exe的所有输出都被读取。即使排除写入StandardInput的步骤
  • 即使字符串的AutoFlush=true,也没有从StandardInput传递给命令
  • 我还尝试了Process.StandardInput.Flush(),它什么也不做 我还尝试将填充有空格的字符串传递给它,长度大于4096,这是缓冲区大小,但没有结果。 什么都没发生!!为什么?

    我在DotNet4.5.2和4.7.1上试过这个

    存在类似的问题,但没有一个答案有效。其他的是用另一种语言实现的。i、 e.Java、Delphi等

    这是我的代码的简化版本:

    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();
    }