Is the following pattern of multi-threaded calls acceptable to a .Net FileStream?
Several threads calling a method like this:
ulong offset = whatever; // different for each thread
byte[] buffer = new byte[8192];
object state = someState; // unique for each call, hence also for each thread
lock(theFile)
{
theFile.Seek(whatever, SeekOrigin.Begin);
IAsyncResult result = theFile.BeginRead(buffer, 0, 8192, AcceptResults, state);
}
if(result.CompletedSynchronously)
{
// is it required for us to call AcceptResults ourselves in this case?
// or did BeginRead already call it for us, on this thread or another?
}
Where AcceptResults
is:
void AcceptResults(IAsyncResult result)
{
lock(theFile)
{
int bytesRead = theFile.EndRead(result);
// if we guarantee that the offset of the original call was at least 8192 bytes from
// the end of the file, and thus all 8192 bytes exist, can the FileStream read still
// actually read fewer bytes than that?
// either:
if(bytesRead != 8192)
{
Panic("Page read borked");
}
// or:
// issue a new call to begin read, moving the offsets into the FileStream and
// the buffer, and decreasing the requested size of the read to whatever remains of the buffer
}
}
I'm confused because the documentation seems unclear to me. For example, the FileStream class says:
Any public static members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
But the documentation for BeginRead seems to contemplate having multiple read requests in flight:
Multiple simultaneous asynchronous requests render the request completion order uncertain.
Are multiple reads permitted to be in flight or not? Writes? Is this the appropriate way to secure the location of the Position
of the stream between the call to Seek and the call to BeginRead
? Or does that lock need to be held all the way to EndRead
, hence only one read or write in flight at a time?
I understand that the callback will occur on a different thread, and my handling of state
, buffer
handle that in a way that would permit multiple in flight reads.
Further, does anyone know where in the documentation to find the answers to these questions? Or an article written by someone in the know? I've been searching and can't find anything.
Relevant documentation:
FileStream class
Seek method
BeginRead method
EndRead
IAsyncResult interface
Edit with some new information
A quick check with Reflector shows that BeginRead does capture the stream position into per-call state (some fields of the NativeOverlapped structure). It appears that EndRead doesn't consult the stream position, at least not in any obvious way. This is not conclusive, obviously, because it could be in a non-obvious way or it could be unsupported by the underlying native API.