unsafe private Task<int> ReadNativeAsync(byte[] bytes, int offset, int numBytes, int numBufferedBytesRead, CancellationToken cancellationToken)
{
AssertCanRead(bytes, offset, numBytes);
Debug.Assert(_useAsyncIO, "ReadNativeAsync doesn't work on synchronous file streams!");
// Create and store async stream class library specific data in the async result
FileStreamCompletionSource completionSource = new FileStreamCompletionSource(this, numBufferedBytesRead, bytes, cancellationToken);
NativeOverlapped* intOverlapped = completionSource.Overlapped;
// Calculate position in the file we should be at after the read is done
if (CanSeek)
{
long len = Length;
// Make sure we are reading from the position that we think we are
VerifyOSHandlePosition();
if (_filePosition + numBytes > len)
{
if (_filePosition <= len)
numBytes = (int)(len - _filePosition);
else
numBytes = 0;
}
// Now set the position to read from in the NativeOverlapped struct
// For pipes, we should leave the offset fields set to 0.
intOverlapped->OffsetLow = unchecked((int)_filePosition);
intOverlapped->OffsetHigh = (int)(_filePosition >> 32);
// When using overlapped IO, the OS is not supposed to
// touch the file pointer location at all. We will adjust it
// ourselves. This isn't threadsafe.
// WriteFile should not update the file pointer when writing
// in overlapped mode, according to MSDN. But it does update
// the file pointer when writing to a UNC path!
// So changed the code below to seek to an absolute
// location, not a relative one. ReadFile seems consistent though.
SeekCore(numBytes, SeekOrigin.Current);
}
// queue an async ReadFile operation and pass in a packed overlapped
int errorCode = 0;
int r = ReadFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode);
// ReadFile, the OS version, will return 0 on failure. But
// my ReadFileNative wrapper returns -1. My wrapper will return
// the following:
// On error, r==-1.
// On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING
// on async requests that completed sequentially, r==0
// You will NEVER RELIABLY be able to get the number of bytes
// read back from this call when using overlapped structures! You must
// not pass in a non-null lpNumBytesRead to ReadFile when using
// overlapped structures! This is by design NT behavior.
if (r == -1 && numBytes != -1)
{
// For pipes, when they hit EOF, they will come here.
if (errorCode == ERROR_BROKEN_PIPE)
{
// Not an error, but EOF. AsyncFSCallback will NOT be
// called. Call the user callback here.
// We clear the overlapped status bit for this special case.
// Failure to do so looks like we are freeing a pending overlapped later.
intOverlapped->InternalLow = IntPtr.Zero;
completionSource.SetCompletedSynchronously(0);
}
else if (errorCode != ERROR_IO_PENDING)
{
if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere.
{
SeekCore(0, SeekOrigin.Current);
}
completionSource.ReleaseNativeResource();
if (errorCode == ERROR_HANDLE_EOF)
{
throw Error.GetEndOfFile();
}
else
{
throw Win32Marshal.GetExceptionForWin32Error(errorCode);
}
}
else
{
// Only once the IO is pending do we register for cancellation
completionSource.RegisterForCancellation();
}
}
else
{
// Due to a workaround for a race condition in NT's ReadFile &
// WriteFile routines, we will always be returning 0 from ReadFileNative
// when we do async IO instead of the number of bytes read,
// irregardless of whether the operation completed
// synchronously or asynchronously. We absolutely must not
// set asyncResult._numBytes here, since will never have correct
// results.
//Console.WriteLine("ReadFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on a separate thread");
}
return completionSource.Task;
}