C# 内存中是否有像文件流一样阻塞的流

C# 内存中是否有像文件流一样阻塞的流,c#,stream,memorystream,C#,Stream,Memorystream,我使用的库要求我提供一个实现此接口的对象: public interface IConsole { TextWriter StandardInput { get; } TextReader StandardOutput { get; } TextReader StandardError { get; } } 然后,该对象的读取器将被库使用: IConsole console = new MyConsole(); int readBytes = console.Stand

我使用的库要求我提供一个实现此接口的对象:

public interface IConsole {
    TextWriter StandardInput { get; }
    TextReader StandardOutput { get; }
    TextReader StandardError { get; }
}
然后,该对象的读取器将被库使用:

IConsole console = new MyConsole();
int readBytes = console.StandardOutput.Read(buffer, 0, buffer.Length);
通常,实现IConsole的类具有来自外部进程的标准输出流。在这种情况下,console.StandardOutput.Read调用通过阻塞工作,直到有一些数据写入StandardOutput流

我要做的是创建一个测试IConsole实现,它使用MemoryStream和echo的任何出现在StandardInput上的内容返回到StandardInput。我试过:

MemoryStream echoOutStream = new MemoryStream();
StandardOutput = new StreamReader(echoOutStream);

但问题是console.StandardOutput.Read将返回0而不是阻塞,直到有一些数据。如果没有可用的数据,我是否可以让MemoryStream阻塞?或者我是否可以使用不同的内存流?

最后,我找到了一种简单的方法,从MemoryStream继承并接管读写方法

public class EchoStream : MemoryStream {

    private ManualResetEvent m_dataReady = new ManualResetEvent(false);
    private byte[] m_buffer;
    private int m_offset;
    private int m_count;

    public override void Write(byte[] buffer, int offset, int count) {
        m_buffer = buffer;
        m_offset = offset;
        m_count = count;
        m_dataReady.Set();
    }

    public override int Read(byte[] buffer, int offset, int count) {
        if (m_buffer == null) {
            // Block until the stream has some more data.
            m_dataReady.Reset();
            m_dataReady.WaitOne();    
        }

        Buffer.BlockCopy(m_buffer, m_offset, buffer, offset, (count < m_count) ? count : m_count);
        m_buffer = null;
        return (count < m_count) ? count : m_count;
    }
}
公共类EchoStream:MemoryStream{
private ManualResetEvent m_dataReady=新的ManualResetEvent(错误);
专用字节[]m_缓冲区;
私人国际货币单位偏移量;
私人国际货币单位计数;
公共重写无效写入(字节[]缓冲区、整数偏移量、整数计数){
m_buffer=缓冲区;
m_offset=偏移量;
m_计数=计数;
m_dataReady.Set();
}
公共重写整型读取(字节[]缓冲区、整型偏移量、整型计数){
if(m_buffer==null){
//阻塞,直到流具有更多数据。
m_dataReady.Reset();
m_dataReady.WaitOne();
}
Buffer.BlockCopy(m_Buffer,m_offset,Buffer,offset,(计数
受您答案的启发,以下是我的多线程、多写版本:

public class EchoStream : MemoryStream
{
    private readonly ManualResetEvent _DataReady = new ManualResetEvent(false);
    private readonly ConcurrentQueue<byte[]> _Buffers = new ConcurrentQueue<byte[]>();

    public bool DataAvailable{get { return !_Buffers.IsEmpty; }}

    public override void Write(byte[] buffer, int offset, int count)
    {
        _Buffers.Enqueue(buffer);
        _DataReady.Set();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        _DataReady.WaitOne();

        byte[] lBuffer;

        if (!_Buffers.TryDequeue(out lBuffer))
        {
            _DataReady.Reset();
            return -1;
        }

        if (!DataAvailable)
            _DataReady.Reset();

        Array.Copy(lBuffer, buffer, lBuffer.Length);
        return lBuffer.Length;
    }
}
公共类EchoStream:MemoryStream
{
私有只读ManualResetEvent _DataReady=新的ManualResetEvent(错误);
私有只读ConcurrentQueue _Buffers=新ConcurrentQueue();
public bool DataAvailable{get{return!\u Buffers.IsEmpty;}
公共重写无效写入(字节[]缓冲区、整数偏移量、整数计数)
{
_Buffers.排队(buffer);
_DataReady.Set();
}
公共重写整型读取(字节[]缓冲区、整型偏移量、整型计数)
{
_DataReady.WaitOne();
字节[]lBuffer;
if(!\u Buffers.TryDequeue(out lBuffer))
{
_DataReady.Reset();
返回-1;
}
如果(!数据可用)
_DataReady.Reset();
复制(lBuffer,buffer,lBuffer.Length);
返回lBuffer.Length;
}
}

对于您的版本,您应该在写入时读取流,而不允许任何连续写入。我的版本在ConcurrentQueue中缓冲任何写入的缓冲区(将其更改为简单队列并锁定非常简单)

我将添加一个更完善的EchoStream版本。这是其他两个版本的组合,加上评论中的一些建议

更新-我已经连续几天用超过50 TB的数据测试了这个EchoStream。测试让它位于网络流和ZStandard压缩流之间。异步也已经过测试,这给表面带来了一种罕见的挂起状态。内置System.IO.Stream似乎不希望在同一个流上同时调用ReadAsync和WriteAsync,如果没有任何可用数据,这可能会导致挂起,因为两个调用使用相同的内部变量。因此,我必须重写这些函数,这解决了悬而未决的问题

此版本具有以下增强功能:

1) 这是使用System.IO.Stream基类而不是MemoryStream从头开始编写的

2) 构造函数可以设置最大队列深度,如果达到此级别,则流写入将阻塞,直到执行读取,从而将队列深度降至最大级别以下(无限制=0,默认值=10)

3) 在读取/写入数据时,缓冲区偏移量和计数现在已生效。此外,您可以使用比Write更小的缓冲区调用Read,而不会引发异常或丢失数据。在循环中使用BlockCopy填充字节,直到满足计数

4) 有一个名为AlwaysCopyBuffer的公共属性,它在Write函数中创建缓冲区的副本。将此设置为true将安全地允许在调用Write后重用字节缓冲区

5) 有一个名为ReadTimeout/WriteTimeout的公共属性,它控制读/写函数在返回0(默认值为无穷,-1)之前阻塞的时间

6) 使用BlockingCollection类,它将ConcurrentQueue和AutoResteEvent类组合在一起。最初我使用的是这两个类,但存在一种罕见的情况,即在数据进入队列()后,当AutoResteEvent允许线程在Read()中通过时,它无法立即使用。这种情况大约每500GB的数据通过它就会发生一次。治疗方法是睡觉并再次检查数据。有时,睡眠(0)起作用,但在CPU使用率很高的极端情况下,在数据显示之前,它可能高达睡眠(1000)。在我切换到BlockingCollection之后,它有很多额外的代码要优雅地处理,而且没有问题

7) 这已被测试为同步异步读写的线程安全

using System;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;

public class EchoStream : Stream
{
    public override bool CanTimeout { get; } = true;
    public override int ReadTimeout { get; set; } = Timeout.Infinite;
    public override int WriteTimeout { get; set; } = Timeout.Infinite;
    public override bool CanRead { get; } = true;
    public override bool CanSeek { get; } = false;
    public override bool CanWrite { get; } = true;

    public bool CopyBufferOnWrite { get; set; } = false;

    private readonly object _lock = new object();

    // Default underlying mechanism for BlockingCollection is ConcurrentQueue<T>, which is what we want
    private readonly BlockingCollection<byte[]> _Buffers;
    private int _maxQueueDepth = 10;

    private byte[] m_buffer = null;
    private int m_offset = 0;
    private int m_count = 0;

    private bool m_Closed = false;
    public override void Close()
    {
        m_Closed = true;

        // release any waiting writes
        _Buffers.CompleteAdding();
    }

    public bool DataAvailable
    {
        get
        {
            return _Buffers.Count > 0;
        }
    }

    private long _Length = 0L;
    public override long Length
    {
        get
        {
            return _Length;
        }
    }

    private long _Position = 0L;
    public override long Position
    {
        get
        {
            return _Position;
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public EchoStream() : this(10)
    {
    }

    public EchoStream(int maxQueueDepth)
    {
        _maxQueueDepth = maxQueueDepth;
        _Buffers = new BlockingCollection<byte[]>(_maxQueueDepth);
    }

    // we override the xxxxAsync functions because the default base class shares state between ReadAsync and WriteAsync, which causes a hang if both are called at once
    public new Task WriteAsync(byte[] buffer, int offset, int count)
    {
        return Task.Run(() => Write(buffer, offset, count));
    }

    // we override the xxxxAsync functions because the default base class shares state between ReadAsync and WriteAsync, which causes a hang if both are called at once
    public new Task<int> ReadAsync(byte[] buffer, int offset, int count)
    {
        return Task.Run(() =>
        {
            return Read(buffer, offset, count);
        });
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        if (m_Closed || buffer.Length - offset < count || count <= 0)
            return;

        byte[] newBuffer;
        if (!CopyBufferOnWrite && offset == 0 && count == buffer.Length)
            newBuffer = buffer;
        else
        {
            newBuffer = new byte[count];
            System.Buffer.BlockCopy(buffer, offset, newBuffer, 0, count);
        }
        if (!_Buffers.TryAdd(newBuffer, WriteTimeout))
            throw new TimeoutException("EchoStream Write() Timeout");

        _Length += count;
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        if (count == 0)
            return 0;
        lock (_lock)
        {
            if (m_count == 0 && _Buffers.Count == 0)
            {
                if (m_Closed)
                    return -1;

                if (_Buffers.TryTake(out m_buffer, ReadTimeout))
                {
                    m_offset = 0;
                    m_count = m_buffer.Length;
                }
                else
                    return m_Closed ? -1 : 0;
            }

            int returnBytes = 0;
            while (count > 0)
            {
                if (m_count == 0)
                {
                    if (_Buffers.TryTake(out m_buffer, 0))
                    {
                        m_offset = 0;
                        m_count = m_buffer.Length;
                    }
                    else
                        break;
                }

                var bytesToCopy = (count < m_count) ? count : m_count;
                System.Buffer.BlockCopy(m_buffer, m_offset, buffer, offset, bytesToCopy);
                m_offset += bytesToCopy;
                m_count -= bytesToCopy;
                offset += bytesToCopy;
                count -= bytesToCopy;

                returnBytes += bytesToCopy;
            }

            _Position += returnBytes;

            return returnBytes;
        }
    }

    public override int ReadByte()
    {
        byte[] returnValue = new byte[1];
        return (Read(returnValue, 0, 1) <= 0 ? -1 : (int)returnValue[0]);
    }

    public override void Flush()
    {
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotImplementedException();
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }
}
使用系统;
使用System.IO;
使用System.Threading.Tasks;
使用系统线程;
使用System.Collections.Concurrent;
公共类EchoStream:Stream
{
公共覆盖boolcantimeout{get;}=true;
public override int ReadTimeout{get;set;}=Timeout.Infinite;
public override int WriteTimeout{get;set;}=Timeout.Infinite;
public override bool CanRead{get;}=true;
公共图书馆
public class EchoStream : MemoryStream
{
    private readonly ManualResetEvent _DataReady = new ManualResetEvent(false);
    private readonly ConcurrentQueue<byte[]> _Buffers = new ConcurrentQueue<byte[]>();

    public bool DataAvailable { get { return !_Buffers.IsEmpty; } }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _Buffers.Enqueue(buffer.Skip(offset).Take(count).ToArray());
        _DataReady.Set();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        _DataReady.WaitOne();

        byte[] lBuffer;

        if (!_Buffers.TryDequeue(out lBuffer))
        {
            _DataReady.Reset();
            return -1;
        }

        if (!DataAvailable)
            _DataReady.Reset();

        Array.Copy(lBuffer, 0, buffer, offset, Math.Min(lBuffer.Length, count));
        return lBuffer.Length;
    }
}