C #: Перенаправить вывод консольного приложения: как сбросить вывод?

Я создаю внешнее консольное приложение и использую асинхронное перенаправление вывода.
как показано в этом сообщении SO

Моя проблема в том, что порожденный процесс должен произвести определенный объем вывода, прежде чем я получу уведомление о событии OutputDataReceived.

Я хочу получить событие OutputDataReceived как можно скорее.

У меня есть простое приложение для перенаправления, и вот некоторые наблюдения:
1. Когда я вызываю простое 'while (true) print ("X");' консольное приложение (C #) Я получаю событие вывода немедленно. 2. Когда я вызываю стороннее приложение, я пытаюсь обернуть из командной строки, я вижу построчный вывод.
3. Когда я вызываю это стороннее приложение с моего голого -bone wrapper (см. 1) - вывод идет кусками (размером около одной страницы).

Что происходит внутри этого приложения?

К вашему сведению: рассматриваемое приложение представляет собой «USBee DX Data Exctarctor (Async bus) v1.0».


person THX-1138    schedule 23.06.2009    source источник


Ответы (3)


Я провел еще несколько исследований и исправил класс Microsofts Process. Но поскольку мой последний ответ был удален без причины, мне пришлось создать новый.

Возьмем этот пример ...

Создайте приложение для Windows и вставьте расширенное текстовое поле в основную форму, а затем добавьте его в загрузку формы ...

        Process p = new Process()
        {
            StartInfo = new ProcessStartInfo()
            {
                FileName = "cmd.exe",
                CreateNoWindow = true,
                UseShellExecute = false,
                ErrorDialog = false,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
            },
            EnableRaisingEvents = true,
            SynchronizingObject = this
        };

        p.OutputDataReceived += (s, ea) => this.richTextBox1.AppendText(ea.Data);

        p.Start();
        p.BeginOutputReadLine();

Это выведет что-то вроде этого ...

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

Событие OutputDataReceived не запускается для последней строки. После некоторого ILSpying выясняется, что это сделано намеренно, потому что последняя строка не заканчивается на crlf, предполагается, что есть еще коммит, и добавляется его в начало следующего события.

Чтобы исправить это, я написал оболочку для класса Process и убрал с ней некоторые из требуемых внутренних классов, чтобы все работало аккуратно. Вот класс FixedProcess ...

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Threading;

namespace System.Diagnostics
{
    internal delegate void UserCallBack(string data);
    public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e);

    public class FixedProcess : Process
    {
        internal AsyncStreamReader output;
        internal AsyncStreamReader error;
        public event DataReceivedEventHandler OutputDataReceived;
        public event DataReceivedEventHandler ErrorDataReceived;

        public new void BeginOutputReadLine()
        {
            Stream baseStream = StandardOutput.BaseStream;
            this.output = new AsyncStreamReader(this, baseStream, new UserCallBack(this.FixedOutputReadNotifyUser), StandardOutput.CurrentEncoding);
            this.output.BeginReadLine();
        }

        public void BeginErrorReadLine()
        {
            Stream baseStream = StandardError.BaseStream;
            this.error = new AsyncStreamReader(this, baseStream, new UserCallBack(this.FixedErrorReadNotifyUser), StandardError.CurrentEncoding);
            this.error.BeginReadLine();
        }

        internal void FixedOutputReadNotifyUser(string data)
        {
            DataReceivedEventHandler outputDataReceived = this.OutputDataReceived;
            if (outputDataReceived != null)
            {
                DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data);
                if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
                {
                    this.SynchronizingObject.Invoke(outputDataReceived, new object[]
                    {
                        this, 
                        dataReceivedEventArgs
                    });
                    return;
                }
                outputDataReceived(this, dataReceivedEventArgs);
            }
        }

        internal void FixedErrorReadNotifyUser(string data)
        {
            DataReceivedEventHandler errorDataReceived = this.ErrorDataReceived;
            if (errorDataReceived != null)
            {
                DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data);
                if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
                {
                    this.SynchronizingObject.Invoke(errorDataReceived, new object[]
                    {
                        this, 
                        dataReceivedEventArgs
                    });
                    return;
                }
                errorDataReceived(this, dataReceivedEventArgs);
            }
        }
    }

    internal class AsyncStreamReader : IDisposable
    {
        internal const int DefaultBufferSize = 1024;
        private const int MinBufferSize = 128;
        private Stream stream;
        private Encoding encoding;
        private Decoder decoder;
        private byte[] byteBuffer;
        private char[] charBuffer;
        private int _maxCharsPerBuffer;
        private Process process;
        private UserCallBack userCallBack;
        private bool cancelOperation;
        private ManualResetEvent eofEvent;
        private Queue messageQueue;
        private StringBuilder sb;
        private bool bLastCarriageReturn;
        public virtual Encoding CurrentEncoding
        {
            get
            {
                return this.encoding;
            }
        }
        public virtual Stream BaseStream
        {
            get
            {
                return this.stream;
            }
        }
        internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding)
            : this(process, stream, callback, encoding, 1024)
        {
        }
        internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize)
        {
            this.Init(process, stream, callback, encoding, bufferSize);
            this.messageQueue = new Queue();
        }
        private void Init(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize)
        {
            this.process = process;
            this.stream = stream;
            this.encoding = encoding;
            this.userCallBack = callback;
            this.decoder = encoding.GetDecoder();
            if (bufferSize < 128)
            {
                bufferSize = 128;
            }
            this.byteBuffer = new byte[bufferSize];
            this._maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
            this.charBuffer = new char[this._maxCharsPerBuffer];
            this.cancelOperation = false;
            this.eofEvent = new ManualResetEvent(false);
            this.sb = null;
            this.bLastCarriageReturn = false;
        }
        public virtual void Close()
        {
            this.Dispose(true);
        }
        void IDisposable.Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposing && this.stream != null)
            {
                this.stream.Close();
            }
            if (this.stream != null)
            {
                this.stream = null;
                this.encoding = null;
                this.decoder = null;
                this.byteBuffer = null;
                this.charBuffer = null;
            }
            if (this.eofEvent != null)
            {
                this.eofEvent.Close();
                this.eofEvent = null;
            }
        }
        internal void BeginReadLine()
        {
            if (this.cancelOperation)
            {
                this.cancelOperation = false;
            }
            if (this.sb == null)
            {
                this.sb = new StringBuilder(1024);
                this.stream.BeginRead(this.byteBuffer, 0, this.byteBuffer.Length, new AsyncCallback(this.ReadBuffer), null);
                return;
            }
            this.FlushMessageQueue();
        }
        internal void CancelOperation()
        {
            this.cancelOperation = true;
        }
        private void ReadBuffer(IAsyncResult ar)
        {
            int num;
            try
            {
                num = this.stream.EndRead(ar);
            }
            catch (IOException)
            {
                num = 0;
            }
            catch (OperationCanceledException)
            {
                num = 0;
            }
            if (num == 0)
            {
                lock (this.messageQueue)
                {
                    if (this.sb.Length != 0)
                    {
                        this.messageQueue.Enqueue(this.sb.ToString());
                        this.sb.Length = 0;
                    }
                    this.messageQueue.Enqueue(null);
                }
                try
                {
                    this.FlushMessageQueue();
                    return;
                }
                finally
                {
                    this.eofEvent.Set();
                }
            }
            int chars = this.decoder.GetChars(this.byteBuffer, 0, num, this.charBuffer, 0);
            this.sb.Append(this.charBuffer, 0, chars);
            this.GetLinesFromStringBuilder();
            this.stream.BeginRead(this.byteBuffer, 0, this.byteBuffer.Length, new AsyncCallback(this.ReadBuffer), null);
        }
        private void GetLinesFromStringBuilder()
        {
            int i = 0;
            int num = 0;
            int length = this.sb.Length;
            if (this.bLastCarriageReturn && length > 0 && this.sb[0] == '\n')
            {
                i = 1;
                num = 1;
                this.bLastCarriageReturn = false;
            }
            while (i < length)
        {
            char c = this.sb[i];
            if (c == '\r' || c == '\n')
            {
                if (c == '\r' && i + 1 < length && this.sb[i + 1] == '\n')
                {
                    i++;
                }

                string obj = this.sb.ToString(num, i + 1 - num);

                num = i + 1;

                lock (this.messageQueue)
                {
                    this.messageQueue.Enqueue(obj);
                }
            }
            i++;
        }

            // Flush Fix: Send Whatever is left in the buffer
            string endOfBuffer = this.sb.ToString(num, length - num);
            lock (this.messageQueue)
            {
                this.messageQueue.Enqueue(endOfBuffer);
                num = length;
            }
            // End Flush Fix

            if (this.sb[length - 1] == '\r')
            {
                this.bLastCarriageReturn = true;
            }
            if (num < length)
            {
                this.sb.Remove(0, num);
            }
            else
            {
                this.sb.Length = 0;
            }
            this.FlushMessageQueue();
        }
        private void FlushMessageQueue()
        {
            while (this.messageQueue.Count > 0)
            {
                lock (this.messageQueue)
                {
                    if (this.messageQueue.Count > 0)
                    {
                        string data = (string)this.messageQueue.Dequeue();
                        if (!this.cancelOperation)
                        {
                            this.userCallBack(data);
                        }
                    }
                    continue;
                }
                break;
            }
        }
        internal void WaitUtilEOF()
        {
            if (this.eofEvent != null)
            {
                this.eofEvent.WaitOne();
                this.eofEvent.Close();
                this.eofEvent = null;
            }
        }
    }

    public class DataReceivedEventArgs : EventArgs
    {
        internal string _data;
        /// <summary>Gets the line of characters that was written to a redirected <see cref="T:System.Diagnostics.Process" /> output stream.</summary>
        /// <returns>The line that was written by an associated <see cref="T:System.Diagnostics.Process" /> to its redirected <see cref="P:System.Diagnostics.Process.StandardOutput" /> or <see cref="P:System.Diagnostics.Process.StandardError" /> stream.</returns>
        /// <filterpriority>2</filterpriority>
        public string Data
        {
            get
            {
                return this._data;
            }
        }
        internal DataReceivedEventArgs(string data)
        {
            this._data = data;
        }
    }
}

Вставьте это в свой проект, а затем измените ...

Process p = new Process()
{
    ....

to

FixedProcess p = new FixedProcess()
{
    ....

Теперь ваше приложение должно отображать что-то вроде этого ...

Microsoft Windows [Version 6.1.7601]

Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Projects\FixedProcess\bin\Debug>

без необходимости вносить какие-либо другие изменения в существующий код. Он также по-прежнему асинхронен и красиво упакован. Единственное предостережение заключается в том, что теперь вы получите несколько событий для большого вывода с потенциальными перерывами между ними, поэтому вам нужно будет справиться с этим сценарием самостоятельно. В остальном все должно быть хорошо.

person Dean North    schedule 26.07.2012
comment
Единственное предостережение заключается в том, что теперь вы получите несколько событий для большого вывода с потенциальными перерывами между ними - как именно это может быть проблемой? Я только что добавил StringBuilder.Append() к событию OutputDataReceived. - person Nyerguds; 25.11.2016
comment
Этот метод иногда дает мне пустой вывод при коротких запусках программы, кстати ... есть ли способ дождаться, пока асинхронный процесс полностью догонит, прежде чем обрабатывать окончательное значение? - person Nyerguds; 25.11.2016
comment
В этой заметке, не должно ли это также переопределить событие Exited для ожидания асинхронных операций? - person Nyerguds; 25.11.2016
comment
У этого расширенного класса есть одно большое преимущество: он добавляет \r\n к блокам выходных данных, считываемых через OutputDataReceived события. Это позволяет правильно обрабатывать перенаправленный вывод. - person Axel Kemper; 25.11.2018
comment
После поиска здесь появилось слишком много ответов, и это сработало. Спасибо @dean nort - person phaneesh; 20.07.2020

Похоже, проблема заключалась в том, что фиктивное приложение было написано на C #, которое автоматически сбрасывает вывод при каждом println, в то время как стороннее приложение было написано на c / c ++ и, следовательно, записывалось только тогда, когда stdoutbuffer был заполнен. Единственное найденное решение - убедиться, что приложение c / c ++ очищается после каждой печати, или установить его буфер равным 0.

person joh    schedule 07.07.2010

Ознакомьтесь с этим ответом.

Как отправить ввод в консоль, как если бы пользователь печатал?

Идея состоит в том, что вы будете получать события, полученные на выходе, когда какое-либо из них выбрасывается после запуска процесса.

person David Basarab    schedule 23.06.2009