public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) { if (array==null) throw new ArgumentNullException("array"); if (offset < 0) throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); if (numBytes < 0) throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); if (array.Length - offset < numBytes) throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); Contract.EndContractBlock(); if (_handle.IsClosed) __Error.FileNotOpen(); #if !FEATURE_PAL && FEATURE_ASYNC_IO if (!_isAsync) return base.BeginWrite(array, offset, numBytes, userCallback, stateObject); if (!CanWrite) __Error.WriteNotSupported(); Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); if (_isPipe) { // Pipes are ----ed up, at least when you have 2 different pipes // that you want to use simultaneously. When redirecting stdout // & stderr with the Process class, it's easy to deadlock your // parent & child processes when doing writes 4K at a time. The // OS appears to use a 4K buffer internally. If you write to a // pipe that is full, you will block until someone read from // that pipe. If you try reading from an empty pipe and // FileStream's BeginRead blocks waiting for data to fill it's // internal buffer, you will be blocked. In a case where a child // process writes to stdout & stderr while a parent process tries // reading from both, you can easily get into a deadlock here. // To avoid this deadlock, don't buffer when doing async IO on // pipes. Contract.Assert(_readPos == 0 && _readLen == 0, "FileStream must not have buffered data here! Pipes should be unidirectional."); if (_writePos > 0) FlushWrite(false); return BeginWriteCore(array, offset, numBytes, userCallback, stateObject); } // Handle buffering. FileStreamAsyncResult asyncResult; if (_writePos==0) { if (_readPos < _readLen) FlushRead(); _readPos = 0; _readLen = 0; } int n = _bufferSize - _writePos; if (numBytes <= n) { if (_writePos==0) _buffer = new byte[_bufferSize]; Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, numBytes); _writePos += numBytes; asyncResult = new FileStreamAsyncResult(); asyncResult._userCallback = userCallback; asyncResult._userStateObject = stateObject; asyncResult._waitHandle = null; asyncResult._isWrite = true; asyncResult._numBufferedBytes = numBytes; asyncResult.CallUserCallback(); return asyncResult; } if (_writePos > 0) FlushWrite(false); return BeginWriteCore(array, offset, numBytes, userCallback, stateObject); #else return base.BeginWrite(array, offset, numBytes, userCallback, stateObject); #endif // !FEATURE_PAL && FEATURE_ASYNC_IO }
unsafe private FileStreamAsyncResult BeginWriteCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) { Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); Contract.Assert(CanWrite, "CanWrite"); Contract.Assert(bytes != null, "bytes != null"); Contract.Assert(_readPos == _readLen, "_readPos == _readLen"); Contract.Assert(_isAsync, "BeginWriteCore doesn't work on synchronous file streams!"); Contract.Assert(offset >= 0, "offset is negative"); Contract.Assert(numBytes >= 0, "numBytes is negative"); // Create and store async stream class library specific data in the // async result FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(); asyncResult._handle = _handle; asyncResult._userCallback = userCallback; asyncResult._userStateObject = stateObject; asyncResult._isWrite = true; // For Synchronous IO, I could go with either a callback and using // the managed Monitor class, or I could create a handle and wait on it. ManualResetEvent waitHandle = new ManualResetEvent(false); asyncResult._waitHandle = waitHandle; // Create a managed overlapped class // We will set the file offsets later Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, asyncResult); // Pack the Overlapped class, and store it in the async result NativeOverlapped* intOverlapped; if (userCallback != null) intOverlapped = overlapped.Pack(IOCallback, bytes); else intOverlapped = overlapped.UnsafePack(null, bytes); asyncResult._overlapped = intOverlapped; if (CanSeek) { // Make sure we set the length of the file appropriately. long len = Length; //Console.WriteLine("BeginWrite - Calculating end pos. pos: "+pos+" len: "+len+" numBytes: "+numBytes); // Make sure we are writing to the position that we think we are if (_exposedHandle) VerifyOSHandlePosition(); if (_pos + numBytes > len) { //Console.WriteLine("BeginWrite - Setting length to: "+(pos + numBytes)); SetLengthCore(_pos + 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)_pos; intOverlapped->OffsetHigh = (int)(_pos>>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("BeginWrite finishing. pos: "+pos+" numBytes: "+numBytes+" _pos: "+_pos+" Position: "+Position); int hr = 0; // queue an async WriteFile operation and pass in a packed overlapped int r = WriteFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr); // 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/ hr==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 hr==3e5) hr: 0x{0:x}", hr); // For pipes, when they are closed on the other side, they will come here. if (hr == ERROR_NO_DATA) { // Not an error, but EOF. AsyncFSCallback will NOT be // called. Call the user callback here. asyncResult.CallUserCallback(); // EndWrite will free the Overlapped struct correctly. } else if (hr != ERROR_IO_PENDING) { if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere. SeekCore(0, SeekOrigin.Current); if (hr == ERROR_HANDLE_EOF) __Error.EndOfFile(); else __Error.WinIOError(hr, String.Empty); } } 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 asyncResult; }
unsafe private FileStreamAsyncResult BeginReadCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject, int numBufferedBytesRead) { Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); Contract.Assert(CanRead, "CanRead"); Contract.Assert(bytes != null, "bytes != null"); Contract.Assert(_writePos == 0, "_writePos == 0"); Contract.Assert(_isAsync, "BeginReadCore doesn't work on synchronous file streams!"); Contract.Assert(offset >= 0, "offset is negative"); Contract.Assert(numBytes >= 0, "numBytes is negative"); // Create and store async stream class library specific data in the // async result FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(); asyncResult._handle = _handle; asyncResult._userCallback = userCallback; asyncResult._userStateObject = stateObject; asyncResult._isWrite = false; // Must set this here to ensure all the state on the IAsyncResult // object is set before we call ReadFile, which gives the OS an // opportunity to run our callback (including the user callback & // the call to EndRead) before ReadFile has returned. asyncResult._numBufferedBytes = numBufferedBytesRead; // For Synchronous IO, I could go with either a callback and using // the managed Monitor class, or I could create a handle and wait on it. ManualResetEvent waitHandle = new ManualResetEvent(false); asyncResult._waitHandle = waitHandle; // Create a managed overlapped class // We will set the file offsets later Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, asyncResult); // Pack the Overlapped class, and store it in the async result NativeOverlapped* intOverlapped; if (userCallback != null) intOverlapped = overlapped.Pack(IOCallback, bytes); else intOverlapped = overlapped.UnsafePack(null, bytes); asyncResult._overlapped = intOverlapped; // 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 if (_exposedHandle) VerifyOSHandlePosition(); if (_pos + numBytes > len) { if (_pos <= len) numBytes = (int) (len - _pos); 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)_pos); intOverlapped->OffsetHigh = (int)(_pos>>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 hr = 0; int r = ReadFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr); // 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/ hr==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 (hr == 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; asyncResult.CallUserCallback(); // EndRead will free the Overlapped struct correctly. } else if (hr != ERROR_IO_PENDING) { if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere. SeekCore(0, SeekOrigin.Current); if (hr == ERROR_HANDLE_EOF) __Error.EndOfFile(); else __Error.WinIOError(hr, String.Empty); } } 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 asyncResult; }