我在SslStream
之上使用TcpClient
。不幸的是,`SslStream``不支持同时从多个线程写入或读取。这就是为什么我在它周围写了我自己的包装器:
private ConcurrentQueue<byte> sendQueue;
private volatile bool oSending;
private readonly object writeLock;
public async void Write(byte[] buffer, int offset, int count)
{
if (oSending)
{
lock (writeLock)
{
foreach (var b in buffer)
{
sendQueue.Enqueue(b);
}
}
}
else
{
oSending = true;
await stream.WriteAsync(buffer, offset, count);
oSending = false;
lock (writeLock)
{
if (sendQueue.Count > 0)
{
Write(sendQueue.ToArray(), 0, sendQueue.Count);
sendQueue = new ConcurrentQueue<byte>();
}
}
}
}
背后的意图如下:
到目前为止,我已经尝试了几种解决方案,但似乎每次发送的数据都太多了。
P.S。:我知道按字节顺序填充队列并不好,但这只是快速而又脏。
更新:我根据Dirk的评论添加了队列删除。
答案 0 :(得分:1)
ConcurrentQueue<T>
的访问权限 - 您不需要,该队列已经是线程安全的if(oSending) {} else {oSending = true}
不是线程安全的。两个线程可能将oSending
读为false,输入else块,并将其设置为true。现在你有两个线程写入流。我的修改:
而不是使用布尔标志,而是使用Monitor.TryEnter
来尝试访问流。如果当前正在写入流,则调用将立即返回 - 并继续写入缓冲区。
实施IDisposable
并确保Dispose
刷新缓冲区。
async Task
更改为async void
。
private readonly ConcurrentQueue<byte> _bufferQueue = new ConcurrentQueue<byte>();
private readonly object _bufferLock = new object();
private readonly object _streamLock = new object();
private readonly MemoryStream stream = new MemoryStream();
public async Task Write(byte[] data, int offset, int count)
{
bool streamLockTaken = false;
try
{
//attempt to acquire the lock - if lock is currently taken, return immediately
Monitor.TryEnter(_streamLock, ref streamLockTaken);
if (streamLockTaken) //write to stream
{
//write data to stream and flush the buffer
await stream.WriteAsync(data, offset, count);
await FlushBuffer();
}
else //write to buffer
{
lock (_bufferLock)
foreach (var b in data)
_bufferQueue.Enqueue(b);
}
}
finally
{
if (streamLockTaken)
Monitor.Exit(_streamLock);
}
}
private async Task FlushBuffer()
{
List<byte> bufferedData = new List<byte>();
byte b;
while (_bufferQueue.TryDequeue(out b))
bufferedData.Add(b);
await stream.WriteAsync(bufferedData.ToArray(), 0, bufferedData.Count);
}
public void Dispose()
{
lock(_streamLock)
FlushBuffer().Wait();
}
答案 1 :(得分:1)
<强>更新强>
使用TPL Dataflow:
using System.Threading.Tasks.Dataflow;
public class DataflowStreamWriter
{
private readonly MemoryStream _stream = new MemoryStream();
private readonly ActionBlock<byte[]> _block;
public DataflowStreamWriter()
{
_block = new ActionBlock<byte[]>(
bytes => _stream.Write(bytes, 0, bytes.Length));
}
public void Write(byte[] data)
{
_block.Post(data);
}
}
这是一种更好的生产者 - 消费者方法。
每当有人将数据写入您的ConcurrentStreamWriter
实例时,该数据将被添加到缓冲区。此方法是线程安全的,并且多个线程可能同时写入数据。这些是您的制作者。
然后,您有一个使用者 - 使用缓冲区中的数据并将其写入流中。
BlockingCollection<T>
用于在生产者和消费者之间进行通信。这样,如果没有人生产,消费者就会闲置。每当制作人开始并向缓冲区写入内容时,消费者就会醒来。
消费者被懒惰地初始化 - 当且仅当某些数据首次可用时才会创建。
public class ConcurrentStreamWriter : IDisposable
{
private readonly MemoryStream _stream = new MemoryStream();
private readonly BlockingCollection<byte> _buffer = new BlockingCollection<byte>(new ConcurrentQueue<byte>());
private readonly object _writeBufferLock = new object();
private Task _flusher;
private volatile bool _disposed;
private void FlushBuffer()
{
//keep writing to the stream, and block when the buffer is empty
while (!_disposed)
_stream.WriteByte(_buffer.Take());
//when this instance has been disposed, flush any residue left in the ConcurrentStreamWriter and exit
byte b;
while (_buffer.TryTake(out b))
_stream.WriteByte(b);
}
public void Write(byte[] data)
{
if (_disposed)
throw new ObjectDisposedException("ConcurrentStreamWriter");
lock (_writeBufferLock)
foreach (var b in data)
_buffer.Add(b);
InitFlusher();
}
public void InitFlusher()
{
//safely create a new flusher task if one hasn't been created yet
if (_flusher == null)
{
Task newFlusher = new Task(FlushBuffer);
if (Interlocked.CompareExchange(ref _flusher, newFlusher, null) == null)
newFlusher.Start();
}
}
public void Dispose()
{
_disposed = true;
if (_flusher != null)
_flusher.Wait();
_buffer.Dispose();
}
}
答案 2 :(得分:0)
你不能只锁定底层流吗?我相信它可以这么简单:
private readonly object writeLock = new Object();
public async void Write(byte[] buffer, int offset, int count)
{
lock (writeLock)
{
await stream.WriteAsync(buffer, offset, count);
}
}
此外,随着您的排队实现,我认为有一个更改,写入可以排队机器人永远不会写入流。例如,在另一个线程已经出列但在该线程释放其锁定之前排队。