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",