private unsafe Task WriteInternalCoreAsync(byte[] bytes, int offset, int numBytes, CancellationToken cancellationToken)
{
Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
Debug.Assert(CanWrite, "_parent.CanWrite");
Debug.Assert(bytes != null, "bytes != null");
Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
Debug.Assert(_useAsyncIO, "WriteInternalCoreAsync doesn't work on synchronous file streams!");
Debug.Assert(offset >= 0, "offset is negative");
Debug.Assert(numBytes >= 0, "numBytes is negative");
// Create and store async stream class library specific data in the async result
FileStreamCompletionSource completionSource = new FileStreamCompletionSource(this, 0, bytes, cancellationToken);
NativeOverlapped* intOverlapped = completionSource.Overlapped;
if (CanSeek)
{
// Make sure we set the length of the file appropriately.
long len = Length;
//Console.WriteLine("WriteInternalCoreAsync - Calculating end pos. pos: "+pos+" len: "+len+" numBytes: "+numBytes);
// Make sure we are writing to the position that we think we are
VerifyOSHandlePosition();
if (_filePosition + numBytes > len)
{
//Console.WriteLine("WriteInternalCoreAsync - Setting length to: "+(pos + numBytes));
SetLengthCore(_filePosition + numBytes);
}
// Now set the position to read from in the NativeOverlapped struct
// For pipes, we should leave the offset fields set to 0.
intOverlapped->OffsetLow = (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.
SeekCore(numBytes, SeekOrigin.Current);
}
//Console.WriteLine("WriteInternalCoreAsync finishing. pos: "+pos+" numBytes: "+numBytes+" _pos: "+_pos+" Position: "+Position);
int errorCode = 0;
// queue an async WriteFile operation and pass in a packed overlapped
int r = WriteFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode);
// WriteFile, the OS version, will return 0 on failure. But
// my WriteFileNative 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
// written back from this call when using overlapped IO! You must
// not pass in a non-null lpNumBytesWritten to WriteFile when using
// overlapped structures! This is ByDesign NT behavior.
if (r == -1 && numBytes != -1)
{
//Console.WriteLine("WriteFile returned 0; Write will complete asynchronously (if errorCode==3e5) errorCode: 0x{0:x}", errorCode);
// For pipes, when they are closed on the other side, they will come here.
if (errorCode == ERROR_NO_DATA)
{
// Not an error, but EOF. AsyncFSCallback will NOT be called.
// Completing TCS and return cached task allowing the GC to collect TCS.
completionSource.SetCompletedSynchronously(0);
return Task.CompletedTask;
}
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 // ERROR_IO_PENDING
{
// 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 WriteFileNative
// when we do async IO instead of the number of bytes written,
// 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("WriteFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on another thread.");
}
return completionSource.Task;
}