diff --git a/src/Common/src/System/IO/StreamHelpers.cs b/src/Common/src/System/IO/StreamHelpers.cs new file mode 100644 index 000000000000..3c34e8e67aba --- /dev/null +++ b/src/Common/src/System/IO/StreamHelpers.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace System.IO +{ + /// Provides methods to help in the implementation of Stream-derived types. + internal static class StreamHelpers + { + /// + /// Provides an implementation usable as an override of Stream.CopyToAsync but that uses the shared + /// ArrayPool for the intermediate buffer rather than allocating a new buffer each time. + /// + /// + /// If/when the base CopyToAsync implementation is changed to use a pooled buffer, + /// this will no longer be necessary. + /// + public static Task ArrayPoolCopyToAsync(Stream source, Stream destination, int bufferSize, CancellationToken cancellationToken) + { + Debug.Assert(source != null); + + if (destination == null) + { + throw new ArgumentNullException("destination"); + } + if (bufferSize <= 0) + { + throw new ArgumentOutOfRangeException("bufferSize", bufferSize, SR.ArgumentOutOfRange_NeedPosNum); + } + + if (!source.CanRead) + { + throw source.CanWrite ? + (Exception)new NotSupportedException(SR.NotSupported_UnreadableStream) : + new ObjectDisposedException(null); // passing null as this is used as part of an instance Stream.CopyToAsync override + } + + if (!destination.CanWrite) + { + throw destination.CanRead ? + (Exception)new NotSupportedException(SR.NotSupported_UnwritableStream) : + new ObjectDisposedException("destination"); + } + + return ArrayPoolCopyToAsyncInternal(source, destination, bufferSize, cancellationToken); + } + + private static async Task ArrayPoolCopyToAsyncInternal(Stream source, Stream destination, int bufferSize, CancellationToken cancellationToken) + { + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try + { + int bytesRead; + while ((bytesRead = await source.ReadAsync(buffer, 0, bufferSize, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + } + } + finally + { + ArrayPool.Shared.Return(buffer, clearArray: true); // TODO: When an overload is available, pass bufferSize so we only clear the used part of the array + } + } + } +} diff --git a/src/System.IO.Compression/src/Resources/Strings.resx b/src/System.IO.Compression/src/Resources/Strings.resx index 130b8d5f1de6..9142e78fae3e 100644 --- a/src/System.IO.Compression/src/Resources/Strings.resx +++ b/src/System.IO.Compression/src/Resources/Strings.resx @@ -120,6 +120,9 @@ Enum value was out of legal range. + + Positive number required. + Reading from the compression stream is not supported. @@ -141,14 +144,14 @@ Failed to construct a huffman tree using the length array. The stream might be corrupted. - - The base stream is not readable. - This operation is not supported. - - The base stream is not writeable. + + Stream does not support reading. + + + Stream does not support writing. Can not access a closed Stream. diff --git a/src/System.IO.Compression/src/System.IO.Compression.csproj b/src/System.IO.Compression/src/System.IO.Compression.csproj index 2448661a66ad..d4a81f317587 100644 --- a/src/System.IO.Compression/src/System.IO.Compression.csproj +++ b/src/System.IO.Compression/src/System.IO.Compression.csproj @@ -38,6 +38,9 @@ Common\System\IO\PathInternal.cs + + Common\System\IO\StreamHelpers.cs + diff --git a/src/System.IO.Compression/src/System/IO/Compression/DeflateStream.cs b/src/System.IO.Compression/src/System/IO/Compression/DeflateStream.cs index a2a92eb809c1..d0c90f0495c1 100644 --- a/src/System.IO.Compression/src/System/IO/Compression/DeflateStream.cs +++ b/src/System.IO.Compression/src/System/IO/Compression/DeflateStream.cs @@ -90,7 +90,7 @@ internal void InitializeInflater(Stream stream, bool leaveOpen, int windowBits) { Debug.Assert(stream != null); if (!stream.CanRead) - throw new ArgumentException(SR.NotReadableStream, "stream"); + throw new ArgumentException(SR.NotSupported_UnreadableStream, "stream"); _inflater = new Inflater(windowBits); @@ -107,7 +107,7 @@ internal void InitializeDeflater(Stream stream, bool leaveOpen, int windowBits, { Debug.Assert(stream != null); if (!stream.CanWrite) - throw new ArgumentException(SR.NotWriteableStream, "stream"); + throw new ArgumentException(SR.NotSupported_UnwritableStream, "stream"); _deflater = new Deflater(compressionLevel, windowBits); @@ -333,6 +333,11 @@ private static void ThrowCannotWriteToDeflateStreamException() throw new InvalidOperationException(SR.CannotWriteToDeflateStream); } + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return StreamHelpers.ArrayPoolCopyToAsync(this, destination, bufferSize, cancellationToken); + } + public override Task ReadAsync(Byte[] array, int offset, int count, CancellationToken cancellationToken) { EnsureDecompressionMode(); @@ -373,7 +378,7 @@ public override Task ReadAsync(Byte[] array, int offset, int count, Cancell readTask = _stream.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken); if (readTask == null) { - throw new InvalidOperationException(SR.NotReadableStream); + throw new InvalidOperationException(SR.NotSupported_UnreadableStream); } return ReadAsyncCore(readTask, array, offset, count, cancellationToken); @@ -422,7 +427,7 @@ private async Task ReadAsyncCore(Task readTask, byte[] array, int offs readTask = _stream.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken); if (readTask == null) { - throw new InvalidOperationException(SR.NotReadableStream); + throw new InvalidOperationException(SR.NotSupported_UnreadableStream); } } else diff --git a/src/System.IO.Compression/src/System/IO/Compression/GZipStream.cs b/src/System.IO.Compression/src/System/IO/Compression/GZipStream.cs index 8b906e17c2a3..7aba2f159bd1 100644 --- a/src/System.IO.Compression/src/System/IO/Compression/GZipStream.cs +++ b/src/System.IO.Compression/src/System/IO/Compression/GZipStream.cs @@ -159,6 +159,11 @@ public Stream BaseStream } } + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return StreamHelpers.ArrayPoolCopyToAsync(this, destination, bufferSize, cancellationToken); + } + public override Task ReadAsync(Byte[] array, int offset, int count, CancellationToken cancellationToken) { CheckDeflateStream(); diff --git a/src/System.IO.Compression/tests/DeflateStreamTests.cs b/src/System.IO.Compression/tests/DeflateStreamTests.cs index c2cdc751172a..175aac0bbfba 100644 --- a/src/System.IO.Compression/tests/DeflateStreamTests.cs +++ b/src/System.IO.Compression/tests/DeflateStreamTests.cs @@ -458,6 +458,23 @@ public void ReadWriteArgumentValidation() } } + [Fact] + public void CopyToAsyncArgumentValidation() + { + using (DeflateStream ds = new DeflateStream(new MemoryStream(), CompressionMode.Decompress)) + { + Assert.Throws("destination", () => { ds.CopyToAsync(null); }); + Assert.Throws("bufferSize", () => { ds.CopyToAsync(new MemoryStream(), 0); }); + Assert.Throws(() => { ds.CopyToAsync(new MemoryStream(new byte[1], writable: false)); }); + ds.Dispose(); + Assert.Throws(() => { ds.CopyToAsync(new MemoryStream()); }); + } + using (DeflateStream ds = new DeflateStream(new MemoryStream(), CompressionMode.Compress)) + { + Assert.Throws(() => { ds.CopyToAsync(new MemoryStream()); }); + } + } + [Fact] public void Precancellation() { diff --git a/src/System.IO.Compression/tests/GZipStreamTests.cs b/src/System.IO.Compression/tests/GZipStreamTests.cs index d4dbade4ccf6..27c7e11a4b00 100644 --- a/src/System.IO.Compression/tests/GZipStreamTests.cs +++ b/src/System.IO.Compression/tests/GZipStreamTests.cs @@ -143,20 +143,7 @@ private static async Task DecompressAsync(MemoryStream compareStream, MemoryStre var zip = new GZipStream(gzStream, CompressionMode.Decompress); var GZipStream = new MemoryStream(); - - int _bufferSize = 1024; - var bytes = new Byte[_bufferSize]; - bool finished = false; - int retCount; - while (!finished) - { - retCount = await zip.ReadAsync(bytes, 0, _bufferSize); - - if (retCount != 0) - await GZipStream.WriteAsync(bytes, 0, retCount); - else - finished = true; - } + await zip.CopyToAsync(GZipStream); GZipStream.Position = 0; compareStream.Position = 0; @@ -225,6 +212,23 @@ public void WriteOnlyStreamThrowsOnDecompress() }); } + [Fact] + public void CopyToAsyncArgumentValidation() + { + using (GZipStream gs = new GZipStream(new MemoryStream(), CompressionMode.Decompress)) + { + Assert.Throws("destination", () => { gs.CopyToAsync(null); }); + Assert.Throws("bufferSize", () => { gs.CopyToAsync(new MemoryStream(), 0); }); + Assert.Throws(() => { gs.CopyToAsync(new MemoryStream(new byte[1], writable: false)); }); + gs.Dispose(); + Assert.Throws(() => { gs.CopyToAsync(new MemoryStream()); }); + } + using (GZipStream gs = new GZipStream(new MemoryStream(), CompressionMode.Compress)) + { + Assert.Throws(() => { gs.CopyToAsync(new MemoryStream()); }); + } + } + [Fact] public void TestCtors() { diff --git a/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj b/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj index b1026b497846..7793d6eb853f 100644 --- a/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj +++ b/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj @@ -55,6 +55,9 @@ Common\System\Collections\Generic\EnumerableHelpers.cs + + Common\System\IO\StreamHelpers.cs + Common\System\IO\StringBuilderCache.cs diff --git a/src/System.IO.FileSystem/src/System/IO/UnixFileStream.cs b/src/System.IO.FileSystem/src/System/IO/UnixFileStream.cs index f7a060110332..e22c802e7229 100644 --- a/src/System.IO.FileSystem/src/System/IO/UnixFileStream.cs +++ b/src/System.IO.FileSystem/src/System/IO/UnixFileStream.cs @@ -763,6 +763,19 @@ private unsafe int ReadNative(byte[] array, int offset, int count) return bytesRead; } + /// + /// Asynchronously reads the bytes from the current stream and writes them to another + /// stream, using a specified buffer size. + /// + /// The stream to which the contents of the current stream will be copied. + /// The size, in bytes, of the buffer. This value must be greater than zero. + /// The token to monitor for cancellation requests. + /// A task that represents the asynchronous copy operation. + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return StreamHelpers.ArrayPoolCopyToAsync(this, destination, bufferSize, cancellationToken); + } + /// /// Asynchronously reads a sequence of bytes from the current stream and advances /// the position within the stream by the number of bytes read. diff --git a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs index 4d8a75b7566d..4de9e79502a6 100644 --- a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs +++ b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs @@ -905,7 +905,7 @@ private void AllocateBuffer() Debug.Assert(_buffer == null); Debug.Assert(_preallocatedOverlapped == null); - _buffer = new byte[_bufferSize]; // TODO: Issue #5598: Use ArrayPool. + _buffer = new byte[_bufferSize]; if (_isAsync) { _preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, _buffer); @@ -1689,6 +1689,11 @@ private int GetLastWin32ErrorAndDisposeHandleIfInvalid(bool throwIfInvalidHandle return errorCode; } + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return StreamHelpers.ArrayPoolCopyToAsync(this, destination, bufferSize, cancellationToken); + } + [System.Security.SecuritySafeCritical] public override Task ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) { diff --git a/src/System.IO.FileSystem/src/project.json b/src/System.IO.FileSystem/src/project.json index 5ecc366ac8a3..bf181868e3bb 100644 --- a/src/System.IO.FileSystem/src/project.json +++ b/src/System.IO.FileSystem/src/project.json @@ -1,5 +1,6 @@ { "dependencies": { + "System.Buffers": "4.0.0-rc3-23808", "System.Collections": "4.0.10", "System.Diagnostics.Contracts": "4.0.0", "System.Diagnostics.Debug": "4.0.10", diff --git a/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs b/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs index 3e3b75bcb6b1..0dbd7fdd9236 100644 --- a/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs +++ b/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs @@ -396,6 +396,23 @@ public Task CopyToAsyncBetweenFileStreams() numWrites: 10); } + [Fact] + public void CopyToAsync_InvalidArgs_Throws() + { + using (FileStream fs = new FileStream(GetTestFilePath(), FileMode.Create)) + { + Assert.Throws("destination", () => { fs.CopyToAsync(null); }); + Assert.Throws("bufferSize", () => { fs.CopyToAsync(new MemoryStream(), 0); }); + Assert.Throws(() => { fs.CopyToAsync(new MemoryStream(new byte[1], writable: false)); }); + fs.Dispose(); + Assert.Throws(() => { fs.CopyToAsync(new MemoryStream()); }); + } + using (FileStream fs = new FileStream(GetTestFilePath(), FileMode.Create, FileAccess.Write)) + { + Assert.Throws(() => { fs.CopyToAsync(new MemoryStream()); }); + } + } + [Theory] [MemberData("MemberData_FileStreamAsyncWriting")] [OuterLoop] // many combinations: we test just one in inner loop and the rest outer diff --git a/src/System.IO.Pipes/src/Resources/Strings.resx b/src/System.IO.Pipes/src/Resources/Strings.resx index e22ba219bffe..baefebc5c486 100644 --- a/src/System.IO.Pipes/src/Resources/Strings.resx +++ b/src/System.IO.Pipes/src/Resources/Strings.resx @@ -168,6 +168,9 @@ maxNumberOfServerInstances must either be a value between 1 and 254, or NamedPipeServerStream.MaxAllowedServerInstances (to obtain the maximum number allowed by system resources). + + Positive number required. + Pipe hasn't been connected yet. diff --git a/src/System.IO.Pipes/src/System.IO.Pipes.csproj b/src/System.IO.Pipes/src/System.IO.Pipes.csproj index 37be4609ae08..33096edaf4b5 100644 --- a/src/System.IO.Pipes/src/System.IO.Pipes.csproj +++ b/src/System.IO.Pipes/src/System.IO.Pipes.csproj @@ -36,6 +36,9 @@ + + Common\System\IO\StreamHelpers.cs + diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs index e870b706f0a0..c0093b164203 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs @@ -373,18 +373,16 @@ private unsafe int ReadCoreWithCancellation(byte[] buffer, int offset, int count if (signaledFdCount != 0) { - // Our pipe is ready. Break out of the loop to read from it. - Debug.Assert((events[0].TriggeredEvents & Interop.Sys.PollEvents.POLLIN) != 0, "Expected revents on read fd to have POLLIN set"); + // Our pipe is ready. Break out of the loop to read from it. The fd may have been signaled due to + // POLLIN (data available), POLLHUP (hang-up), POLLERR (some error on the stream), etc... any such + // data will be propagated to us when we do the actual read. break; } } // Read it. - Debug.Assert((events[0].TriggeredEvents & Interop.Sys.PollEvents.POLLIN) != 0); int result = CheckPipeCall(Interop.Sys.Read(_handle, bufPtr + offset, count)); - Debug.Assert(result <= count); - - Debug.Assert(result >= 0); + Debug.Assert(result >= 0 && result <= count, "Expected 0 <= result <= count bytes, got " + result); // return what we read. return result; diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs index 6aaaea1e9d70..5a4c2f07fc48 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs @@ -110,6 +110,11 @@ internal void InitializeHandle(SafePipeHandle handle, bool isExposed, bool isAsy _isFromExistingHandle = isExposed; } + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return StreamHelpers.ArrayPoolCopyToAsync(this, destination, bufferSize, cancellationToken); + } + [SecurityCritical] public override int Read([In, Out] byte[] buffer, int offset, int count) { diff --git a/src/System.IO.Pipes/src/project.json b/src/System.IO.Pipes/src/project.json index d0b32ea57c3d..4e0d59a4240b 100644 --- a/src/System.IO.Pipes/src/project.json +++ b/src/System.IO.Pipes/src/project.json @@ -1,5 +1,6 @@ { "dependencies": { + "System.Buffers": "4.0.0-rc3-23808", "System.Diagnostics.Contracts": "4.0.0", "System.Diagnostics.Debug": "4.0.10", "System.Diagnostics.Tools": "4.0.0", diff --git a/src/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Read.cs b/src/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Read.cs index 471fcbed86c0..c59ac2c4c489 100644 --- a/src/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Read.cs +++ b/src/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Read.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace System.IO.Pipes.Tests { - public class AnonymousPipeTest_Read_ServerIn_ClientOut : PipeTest_Read + public class AnonymousPipeTest_Read_ServerIn_ClientOut : AnonymousPipeTest_Read { protected override ServerClientPair CreateServerClientPair() { @@ -17,7 +19,7 @@ protected override ServerClientPair CreateServerClientPair() } } - public class AnonymousPipeTest_Read_ServerOut_ClientIn : PipeTest_Read + public class AnonymousPipeTest_Read_ServerOut_ClientIn : AnonymousPipeTest_Read { protected override ServerClientPair CreateServerClientPair() { @@ -27,4 +29,33 @@ protected override ServerClientPair CreateServerClientPair() return ret; } } + + public abstract class AnonymousPipeTest_Read : PipeTest_Read + { + [Theory] + [MemberData("AsyncReadWriteChain_MemberData")] + public async Task AsyncReadWriteChain_CopyToAsync(int iterations, int writeBufferSize, int readBufferSize, bool cancelableToken) + { + var writeBuffer = new byte[writeBufferSize * iterations]; + new Random().NextBytes(writeBuffer); + var cancellationToken = cancelableToken ? new CancellationTokenSource().Token : CancellationToken.None; + + using (ServerClientPair pair = CreateServerClientPair()) + { + var readData = new MemoryStream(); + Task copyTask = pair.readablePipe.CopyToAsync(readData, readBufferSize, cancellationToken); + + for (int iter = 0; iter < iterations; iter++) + { + await pair.writeablePipe.WriteAsync(writeBuffer, iter * writeBufferSize, writeBufferSize, cancellationToken); + } + pair.writeablePipe.Dispose(); + + await copyTask; + Assert.Equal(writeBuffer.Length, readData.Length); + Assert.Equal(writeBuffer, readData.ToArray()); + } + } + } + } diff --git a/src/System.IO.Pipes/tests/PipeTest.Read.cs b/src/System.IO.Pipes/tests/PipeTest.Read.cs index 519bb5286723..7894e7f2655c 100644 --- a/src/System.IO.Pipes/tests/PipeTest.Read.cs +++ b/src/System.IO.Pipes/tests/PipeTest.Read.cs @@ -191,6 +191,21 @@ public void ReadOnDisposedReadablePipe_Throws_ObjectDisposedException() } } + [Fact] + public void CopyToAsync_InvalidArgs_Throws() + { + using (ServerClientPair pair = CreateServerClientPair()) + { + Assert.Throws("destination", () => { pair.readablePipe.CopyToAsync(null); }); + Assert.Throws("bufferSize", () => { pair.readablePipe.CopyToAsync(new MemoryStream(), 0); }); + Assert.Throws(() => { pair.readablePipe.CopyToAsync(new MemoryStream(new byte[1], writable: false)); }); + if (!pair.writeablePipe.CanRead) + { + Assert.Throws(() => { pair.writeablePipe.CopyToAsync(new MemoryStream()); }); + } + } + } + [Fact] public virtual async Task ReadFromPipeWithClosedPartner_ReadNoBytes() { @@ -262,7 +277,7 @@ public void ValidWriteByte_ValidReadByte() [Theory] [MemberData("AsyncReadWriteChain_MemberData")] - public async Task AsyncReadWriteChain(int iterations, int writeBufferSize, int readBufferSize, bool cancelableToken) + public async Task AsyncReadWriteChain_ReadWrite(int iterations, int writeBufferSize, int readBufferSize, bool cancelableToken) { var writeBuffer = new byte[writeBufferSize]; var readBuffer = new byte[readBufferSize]; diff --git a/src/System.IO.UnmanagedMemoryStream/src/Resources/Strings.resx b/src/System.IO.UnmanagedMemoryStream/src/Resources/Strings.resx index 6080f3b5d270..c7fd63a08648 100644 --- a/src/System.IO.UnmanagedMemoryStream/src/Resources/Strings.resx +++ b/src/System.IO.UnmanagedMemoryStream/src/Resources/Strings.resx @@ -153,6 +153,9 @@ Non negative number is required. + + Positive number required. + The position may not be greater or equal to the capacity of the accessor. diff --git a/src/System.IO.UnmanagedMemoryStream/src/System.IO.UnmanagedMemoryStream.csproj b/src/System.IO.UnmanagedMemoryStream/src/System.IO.UnmanagedMemoryStream.csproj index d6a75d567f27..b6e63b64893a 100644 --- a/src/System.IO.UnmanagedMemoryStream/src/System.IO.UnmanagedMemoryStream.csproj +++ b/src/System.IO.UnmanagedMemoryStream/src/System.IO.UnmanagedMemoryStream.csproj @@ -17,6 +17,9 @@ + + Common\System\IO\StreamHelpers.cs + diff --git a/src/System.IO.UnmanagedMemoryStream/src/System/IO/UnmanagedMemoryStream.cs b/src/System.IO.UnmanagedMemoryStream/src/System/IO/UnmanagedMemoryStream.cs index 70b3879a8700..d80e0d66c42e 100644 --- a/src/System.IO.UnmanagedMemoryStream/src/System/IO/UnmanagedMemoryStream.cs +++ b/src/System.IO.UnmanagedMemoryStream/src/System/IO/UnmanagedMemoryStream.cs @@ -491,6 +491,19 @@ public override int Read([In, Out] byte[] buffer, int offset, int count) return nInt; } + /// + /// Asynchronously reads the bytes from the current stream and writes them to another + /// stream, using a specified buffer size. + /// + /// The stream to which the contents of the current stream will be copied. + /// The size, in bytes, of the buffer. This value must be greater than zero. + /// The token to monitor for cancellation requests. + /// A task that represents the asynchronous copy operation. + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return StreamHelpers.ArrayPoolCopyToAsync(this, destination, bufferSize, cancellationToken); + } + /// /// Reads bytes from stream and puts them into the buffer /// diff --git a/src/System.IO.UnmanagedMemoryStream/src/project.json b/src/System.IO.UnmanagedMemoryStream/src/project.json index a23689d756ad..f1ad8e9f3a51 100644 --- a/src/System.IO.UnmanagedMemoryStream/src/project.json +++ b/src/System.IO.UnmanagedMemoryStream/src/project.json @@ -1,5 +1,6 @@ { "dependencies": { + "System.Buffers": "4.0.0-rc3-23808", "System.Diagnostics.Contracts": "4.0.0", "System.Diagnostics.Debug": "4.0.10", "System.Diagnostics.Tools": "4.0.0",