我通过创建自己的 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();
}
}
}
}