【问题标题】:How to get all available characters from a PipeStream and return immediately?如何从 PipeStream 中获取所有可用字符并立即返回?
【发布时间】:2021-05-02 18:49:55
【问题描述】:

我正在尝试将部分应用程序的控制台输出路由到文本框。问题与Elegant Log Window in WinForms C# 类似,只是我想捕获来自不同流(控制台输出)的所有输出并将其显示在文本框中,而不是显式调用记录器方法。

我的计划是使用 System.IO.Pipes.PipeStream 来实现它。控制台输出将被重新路由为 PipeStream 的输入。 PipeStream 的输出将从表单上的计时器定期读取(例如每秒一次),并更新到 TextBox,丢弃旧文本以保持在特定字符限制下,但允许用户向后滚动 200 行.

我像这样初始化 PipeStream:

        private void initPipelines()
        {
            pipeServer = new AnonymousPipeServerStream();
            pipeClient = new AnonymousPipeClientStream(pipeServer.GetClientHandleAsString());
            textWriter_ = new StreamWriter(pipeServer);
            textReader_ = new StreamReader(pipeClient);
            textWriter_.AutoFlush = true;
        }

每秒运行一次以更新 TextBox 中的文本的例程如下所示:

        private void updateText()
        {
            // Get the characters written since the last update
            // I tried this but it always waits:
            // string message = textReader_.ReadToEnd();
            // I tried this but it also waits when there are fewer than 1024 characters available:
            int messageLength = textReader_.ReadBlock(buffer, 0, 1024);
            string message = new string(buffer, 0, messageLength);

            // ... Add the message to the text box and get rid of old text ...
            
        }

在我的测试例程中,我只是简单地制作了第二个计时器,它将文本写入 textWriter_ 每 100 到 250 毫秒。

我的问题是对 textReader_.ReadToEnd() 的调用只是等待,显然是因为 textWriter_ 流仍处于打开状态,并且如果要读取的字符数少于填充缓冲区。

我想要一个管道方法,它可以简单地读取所有可用字符并立即返回,但我一直无法找到这样的函数。有没有办法做到这一点?或者有什么方法可以确定有多少字符可以立即可用?

我需要逐个字符地获取控制台输出,而不是基于行,因为某些输出是“穷人的进度条”,将 X 输出到屏幕以记录应用程序的进度。所有这些都是我无法轻易影响的。

我认为最好使用 System 类来完成两个流之间的缓冲,而不是尝试自己滚动,但我开始认为这可能是唯一的方法。

【问题讨论】:

    标签: c# pipe


    【解决方案1】:

    这里的问题不在于管道对象。这是你包裹它的StreamReader

    StreamReader 提供四种使用解码文本的方式:一个字符、一个字符缓冲区、一行或一次完整的流。后两种方法不适合您的方案,但其他两种方法可行。

    ReadBlock() 方法是此方法的一种变体(特别是第二种技术),但正如方法名称所示,it blocks until the buffer has been filled or the end of the stream is reached。相反,您应该使用非阻塞 Read() 方法。这将返回当前可用的任何字符,除了它将阻塞足够长的时间以供读取 something (即,如果已到达流的末尾,它将仅返回字符计数为 0)。

    请注意,所写的文档具有误导性。尽管此方法实际上会在未填充提供的缓冲区的情况下返回,即使尚未到达流的末尾,但当前描述为 “此方法在读取 count 参数指定的字符数后返回,或者到达文件末尾。” 这看起来像是复制/粘贴错误,可能是在 .NET 4.5 中引入 ReadBlock() 方法时发生的,他们认为需要添加说明。

    【讨论】:

    • 非常感谢这解释了我的困难。我将“ReadBlock”名称解释为它读取一个字符块,而不是它阻塞。但是如果 Read() 方法一直等到真的有可用的字符,那么我想也不会为我的目的工作,因为写入过程可能很忙并且没有写入任何内容,这将导致我的表单继续等待更新方法,而不是对任何其他输入做出反应。如果不实际调用 Read(),我将无法判断是否还有更多要阅读的内容,这会导致它等待至少 1 个字符,对吧?
    • 如果你需要 100% 的异步行为,你应该使用异步 API。 IE。 ReadAsync()。如果您以前从未编写过异步代码,将代码更改为异步可能会涉及一些学习曲线,但无论如何,这是您应该具备的技能,并且可以在这里完全解决您的问题。
    • ReadAsync() 方法会立即返回,但在读取指定数量的字符或其内部缓冲区已满之前也不会为您提供任何字符,因此这不能解决我的问题.我最终通过创建自己的 WritableRingBuffer 类解决了这个问题,该类派生自 TextWriter,但还包括一个方法 public string getAllAvailableCharacters(),它立即返回所有可用字符(或空字符串),但总是立即返回。我实际上是通过 BackgroundWorker 异步使用它。
    【解决方案2】:

    我通过创建自己的 WritableRingBuffer 类解决了这个问题,该类派生自 TextWriter,但还包含一个方法 public string getAllAvailableCharacters(),它立即返回所有可用字符,如果没有则返回空字符串。

    这是它的代码,也许其他人会觉得它有用。

    using System;
    using System.IO;
    using System.Text;
    
    namespace CPHUtils
    {
        /// <summary>
        /// Class allows writing to a character buffer from which the text can be retrieved
        /// by another object at a later point. It is designed as a ring buffer
        /// with a constant size given at construction. If the writer overtakes the reading of the
        /// characters, the oldest data in the buffer is overwritten, resulting in loss
        /// of data, but preserving being able to read the most recently written characters.
        /// The buffer is designed to be used for example to capture console output
        /// so it can be read by another part of the program or output to a text box.
        /// It locks on an internal object during reading and writing so that it can
        /// be used when the reading and writing processes are running different threads.
        /// </summary>
        public class WritableRingBuffer : TextWriter
        {
    
            private const int defaultBufferSize = 16384;
            private const int minimumBufferSize = 128;
    
            private readonly object lockObject = new object();
    
            public int bufferSize { get; private set; }
    
            private char[] buffer;
            /// <summary>
            /// The 0-based index in the buffer of the next character to read.
            /// </summary>
            private int nextReadIx = 0;
    
            /// <summary>
            /// The 0-based index in the buffer of the position where
            /// the next character should be written.
            /// If the value is the same as
            /// <see cref="nextReadIx"/> and the <see cref="atLimit"/> flag is not set, 
            /// then there are no characters available
            /// to read and the buffer is empty.
            /// If the value is numerically less than <see cref="nextReadIx"/>, then
            /// the filled part of the buffer ranges from this index to the end of
            /// the buffer, and again from the start of the buffer up to the index one
            /// less than <see cref="nextReadIx"/>.<br/>
            /// Example:<br/>
            /// Buffer size = 10 (not normally allowed but makes the example easier).<br/>
            /// After construction (. = free, X = data written):<br/>
            /// nextWriteIx == 0   nextReadIx = 0 (..........)<br/>
            /// 5 characters are written.<br/>
            /// nextWriteIx == 5   nextReadIx = 0 (XXXXX.....)<br/>
            /// 4 characters are read<br/>
            /// nextWriteIx == 5   nextReadIx = 4 (....X.....)<br/>
            /// 7 characters are written. The buffer is filled to the end and starts again at the beginning
            /// There are two free spots at index 2 and 3. <br/>
            /// nextWriteIx == 2   nextReadIx = 4 (XX..XXXXXX)<br/>
            /// Another 7 characters are written. The buffer overflows and overwrites 5 characters.
            /// The oldest character is at index 9, but to distinguish this case from the empty buffer the
            /// <see cref="atLimit"/> flag is set.<br/>
            /// nextWriteIx == 9   nextReadIx = 9 (XXXXXXXXXX)<br/>
            /// 10 characters are read. The character at index 9 is returned first, then at indices 0 to 8. 
            /// The <see cref="atLimit"/> flag is reset.<br/>
            /// nextWriteIx == 9   nextReadIx = 9 (..........)<br/>
            /// </summary>
            private int nextWriteIx = 0;
    
            public bool atLimit { get; private set; } = false;
    
            public int numberOfCharactersLost { get; private set; } = 0;
    
            public WritableRingBuffer()
            {
                init(defaultBufferSize);
            }
    
            /// <summary>
            /// Creates the writable ring buffer with a particular buffer size
            /// </summary>
            /// <param name="bufferSize_">Size of the internal buffer.</param>
            public WritableRingBuffer(int bufferSize_)
            {
                init(bufferSize_);
            }
    
            public WritableRingBuffer(IFormatProvider formatProvider) : base(formatProvider) 
            {
                init(defaultBufferSize);
            }
    
            private void init(int bufferSize_)
            {
                if (bufferSize_ < minimumBufferSize)
                {
                    bufferSize = minimumBufferSize;
                }
                else
                {
                    bufferSize = bufferSize_;
                }
                buffer = new char[bufferSize];
            }
    
            public override Encoding Encoding 
            {
                get
                {
                    return Encoding.UTF8;
                }
            }
    
            public bool hasCharactersToRead
            {
                get
                {
                    lock (lockObject)
                    {
                        return ((nextWriteIx != nextReadIx) || atLimit);
                    }
                }
            }
    
            public int numberAvailableToRead
            {
                get
                {
                    lock (lockObject)
                    {
                        if (nextWriteIx == nextReadIx)
                        {
                            if (atLimit)
                            {
                                return bufferSize;
                            }
                            return 0;
                        }
                        return (nextWriteIx + bufferSize - nextReadIx) % bufferSize; 
                    }
                }
            }
    
            public int numberAvailableToWrite
            {
                get
                {
                    lock (lockObject)
                    {
                        return bufferSize - numberAvailableToRead; 
                    }
                }
            }
    
            protected override void Dispose(bool disposing)
            {
                buffer = null;
                base.Dispose(disposing);
            }
    
            public override void Flush()
            {
                lock (lockObject)
                {
                    base.Flush(); 
                }
            }
    
            public override void Write(char value)
            {
                lock (lockObject)
                {
                    buffer[nextWriteIx++] = value;
                    nextWriteIx = nextWriteIx % bufferSize;
                    // Was the buffer overflowed before the write? Then also increment read index
                    if (atLimit)
                    {
                        nextReadIx = nextWriteIx;
                        numberOfCharactersLost++;
                    }
                    else
                    {
                        // Has the buffer now become overflowed?
                        if (nextWriteIx == nextReadIx)
                        {
                            atLimit = true;
                        }
                    } 
                }
            }
    
            public override void Write(char[] buffer, int index, int count)
            {
                if (buffer == null) throw new ArgumentNullException(nameof(buffer));
                if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
                if (index < 0) throw new ArgumentOutOfRangeException(nameof(index));
                if ((buffer.Length - index) < count) throw new ArgumentException(string.Format("The {3} length == {0} minus {4} == {1} is less than {5} == {2}",
                      buffer.Length, index, count, nameof(buffer), nameof(index), nameof(count)));
    
                lock (lockObject)
                {
                    // Does the request itself already exceed the buffer size?
                    if (count > bufferSize)
                    {
                        int newStart = index + count - bufferSize;
                        Write(buffer, newStart, bufferSize);
                        atLimit = true;
                        nextReadIx = nextWriteIx;
                        numberOfCharactersLost += (count - bufferSize);
                        return;
                    }
    
                    // Space available before starting to write.
                    int freeSpacesBeforeStarting = numberAvailableToWrite;
    
                    // First chunk: starting at the write index possibly up to the end of the buffer
                    int chunk1Count = Math.Min(count, bufferSize - nextWriteIx);
                    Array.Copy(buffer, index, this.buffer, nextWriteIx, chunk1Count);
                    int remaining = count - chunk1Count;
    
                    // Anything left to copy?
                    if (remaining > 0)
                    {
                        int newStart = index + chunk1Count;
                        Array.Copy(buffer, newStart, this.buffer, 0, remaining);
                    }
    
                    // New write index
                    nextWriteIx = (nextWriteIx + count) % bufferSize;
    
                    // Did the buffer hit its limit?
                    if (freeSpacesBeforeStarting <= count)
                    {
                        atLimit = true;
                        nextReadIx = nextWriteIx;
                        numberOfCharactersLost += (count - freeSpacesBeforeStarting);
                    } 
                }
            }
    
            public override void Write(string value)
            {
                if (string.IsNullOrEmpty(value)) return;
    
                int count = value.Length;
    
                lock (lockObject)
                {
                    // Does the request itself already exceed the buffer size?
                    if (count > bufferSize)
                    {
                        int newStart = count - bufferSize;
                        Write(value.Substring(newStart));
                        atLimit = true;
                        nextReadIx = nextWriteIx;
                        numberOfCharactersLost += (count - bufferSize);
                        return;
                    }
    
                    // Space available before starting to write.
                    int freeSpacesBeforeStarting = numberAvailableToWrite;
    
                    // First chunk: as many characters as possible up to the end of the buffer
                    int chunk1Count = Math.Min(count, bufferSize - nextWriteIx);
                    value.CopyTo(0, this.buffer, nextWriteIx, chunk1Count);
                    int remaining = count - chunk1Count;
    
                    // Anything left to copy?
                    if (remaining > 0)
                    {
                        value.CopyTo(chunk1Count, this.buffer, 0, remaining);
                    }
    
                    // New write index
                    nextWriteIx = (nextWriteIx + count) % bufferSize;
    
                    // Did the buffer hit its limit?
                    if (freeSpacesBeforeStarting <= count)
                    {
                        atLimit = true;
                        nextReadIx = nextWriteIx;
                        numberOfCharactersLost += (count - freeSpacesBeforeStarting);
                    } 
                }
            }
    
            public string getAllAvailableCharacters()
            {
                lock (lockObject)
                {
                    if (!hasCharactersToRead)
                    {
                        return string.Empty;
                    }
    
                    // Nonfragmented case
                    if (nextReadIx < nextWriteIx)
                    {
                        string result = new string(buffer, nextReadIx, numberAvailableToRead);
                        atLimit = false;
                        nextReadIx = nextWriteIx;
                        return result;
                    }
    
                    // Fragmented case
                    StringBuilder sb = new StringBuilder(numberAvailableToRead);
                    int chunk1Count = (bufferSize - nextReadIx);
                    sb.Append(buffer, nextReadIx, chunk1Count);
                    int remaining = numberAvailableToRead - chunk1Count;
                    sb.Append(buffer, 0, remaining);
                    atLimit = false;
                    nextReadIx = nextWriteIx;
                    return sb.ToString(); 
                }
            }
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-03
      • 1970-01-01
      • 1970-01-01
      • 2021-04-30
      • 2015-08-25
      • 1970-01-01
      • 2021-07-15
      • 2014-05-27
      相关资源
      最近更新 更多