问题
I'm writing a class that exposes a subsection of a stream for reading. Since data may be read from multiple different subsections at the same time, only one operation may be active at any one time.
I had the idea of locking the underlying stream before every operation. Is locking the stream around the BeginRead
call sufficient to ensure that concurrent asynchronous reads from different positions in the underlying stream happen correctly?
public sealed class SubStream : Stream
{
// ...
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count,
AsyncCallback callback, object state)
{
lock (this.baseStream)
{
this.baseStream.Seek(this.offset + this.position, SeekOrigin.Begin);
return this.baseStream.BeginRead(buffer, offset, count,
callback, state);
}
}
public override int EndRead(IAsyncResult asyncResult)
{
int read;
lock (this.baseStream)
{
read = baseStream.EndRead(asyncResult);
this.position += read;
}
return read;
}
// Read() and ReadByte() also lock on this.baseStream (not shown).
// ...
}
For example, if thread A calls BeginRead
, a lock on the base stream is acquired. Now thread B calls BeginRead
and has to wait for the lock to be released. Thread A sets the position of the base stream and starts an asynchronous read operation. Then releases the lock. Thread B then acquires the lock and changes the position of the base stream and starts another asynchronous read operation. Then, sometime later, the asynchronous read from thread A completes. Can I be sure that this reads from the original position in the base stream? If not, how do I fix it?
回答1:
Here you might end up with multiple threads calling BeginRead
on the same instance of resource (baseStream
). As per MSDN, the "EndRead must be called exactly once for every call to BeginRead. Failing to end a read process before beginning another read can cause undesirable behavior such as deadlock." In you case, I recon a trouble 'if Thread B
is on Seek
(on baseStream) while Thread A
in the middle of executing their EndRead(callback)
'.
Due to the nature of requirement, you are better off with wrapping multi-threaded access with synchronous I/O. This means, the current implementation can be amended with synchronous I/O instead of asynchronous I/O. Also, you may want to consider informing queuing threads about the completion of former threads using Monitor.WaitOne (baseStream)
and Monitor.Pulse(baseStream)
or Monitor.PulseAll(baseStream)
.
Alternately, I would like to throw another idea of Memory-Mapped file for segmented style.
回答2:
In the given code snippet you'll read multiple times from the same position. Move the position update to the BeginRead
function. Apart from that you are honoring the contract of the FileStream
class by never concurrently calling its methods.
来源:https://stackoverflow.com/questions/22779526/sequential-asynchronous-reads-using-stream-beginread