未验证 提交 8cb4e934 编写于 作者: E Eric Erhardt 提交者: GitHub

Override ReadAsync and WriteAsync methods on ConsoleStream. (#71971)

* Override ReadAsync and WriteAsync methods on ConsoleStream.

The base Stream class implements these overloads by renting a buffer,
creating a ReadWriteTask, and copying data as necessary.

Instead, ConsoleStreams can just override these Async methods and synchronously
call the underlying OS API.

Add tests to verify that input and output console streams behave correctly.

* Fix stdin tests to only run on supported platforms.
上级 c5005e00
......@@ -3,6 +3,8 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace System.IO
{
......@@ -28,6 +30,46 @@ public override void Write(byte[] buffer, int offset, int count)
public override void WriteByte(byte value) => Write(new ReadOnlySpan<byte>(in value));
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateWrite(buffer, offset, count);
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
try
{
Write(new ReadOnlySpan<byte>(buffer, offset, count));
return Task.CompletedTask;
}
catch (Exception ex)
{
return Task.FromException(ex);
}
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
ValidateCanWrite();
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled(cancellationToken);
}
try
{
Write(buffer.Span);
return ValueTask.CompletedTask;
}
catch (Exception ex)
{
return ValueTask.FromException(ex);
}
}
public override int Read(byte[] buffer, int offset, int count)
{
ValidateRead(buffer, offset, count);
......@@ -41,6 +83,44 @@ public override int ReadByte()
return result != 0 ? b : -1;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateRead(buffer, offset, count);
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
}
try
{
return Task.FromResult(Read(new Span<byte>(buffer, offset, count)));
}
catch (Exception exception)
{
return Task.FromException<int>(exception);
}
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
ValidateCanRead();
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled<int>(cancellationToken);
}
try
{
return ValueTask.FromResult(Read(buffer.Span));
}
catch (Exception exception)
{
return ValueTask.FromException<int>(exception);
}
}
protected override void Dispose(bool disposing)
{
_canRead = false;
......@@ -74,7 +154,11 @@ public override void Flush()
protected void ValidateRead(byte[] buffer, int offset, int count)
{
ValidateBufferArguments(buffer, offset, count);
ValidateCanRead();
}
private void ValidateCanRead()
{
if (!_canRead)
{
throw Error.GetReadNotSupported();
......@@ -84,7 +168,11 @@ protected void ValidateRead(byte[] buffer, int offset, int count)
protected void ValidateWrite(byte[] buffer, int offset, int count)
{
ValidateBufferArguments(buffer, offset, count);
ValidateCanWrite();
}
private void ValidateCanWrite()
{
if (!_canWrite)
{
throw Error.GetWriteNotSupported();
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
public class ConsoleStreamTests
{
[Fact]
public void WriteToOutputStream_EmptyArray()
{
Stream outStream = Console.OpenStandardOutput();
outStream.Write(new byte[] { }, 0, 0);
}
[ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))]
public void ReadAsyncRespectsCancellation()
{
Stream inStream = Console.OpenStandardInput();
CancellationTokenSource cts = new CancellationTokenSource();
cts.Cancel();
byte[] buffer = new byte[1024];
Task result = inStream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
Assert.True(result.IsCanceled);
ValueTask<int> valueTaskResult = inStream.ReadAsync(buffer.AsMemory(), cts.Token);
Assert.True(valueTaskResult.IsCanceled);
}
[ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))]
public void ReadAsyncHandlesInvalidParams()
{
Stream inStream = Console.OpenStandardInput();
byte[] buffer = new byte[1024];
Assert.Throws<ArgumentNullException>(() => { inStream.ReadAsync(null, 0, buffer.Length); });
Assert.Throws<ArgumentOutOfRangeException>(() => { inStream.ReadAsync(buffer, -1, buffer.Length); });
Assert.Throws<ArgumentOutOfRangeException>(() => { inStream.ReadAsync(buffer, 0, buffer.Length + 1); });
}
[Fact]
public void WriteAsyncRespectsCancellation()
{
Stream outStream = Console.OpenStandardOutput();
CancellationTokenSource cts = new CancellationTokenSource();
cts.Cancel();
byte[] bytes = Encoding.ASCII.GetBytes("Hi");
Task result = outStream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
Assert.True(result.IsCanceled);
ValueTask valueTaskResult = outStream.WriteAsync(bytes.AsMemory(), cts.Token);
Assert.True(valueTaskResult.IsCanceled);
}
[Fact]
public void WriteAsyncHandlesInvalidParams()
{
Stream outStream = Console.OpenStandardOutput();
byte[] bytes = Encoding.ASCII.GetBytes("Hi");
Assert.Throws<ArgumentNullException>(() => { outStream.WriteAsync(null, 0, bytes.Length); });
Assert.Throws<ArgumentOutOfRangeException>(() => { outStream.WriteAsync(bytes, -1, bytes.Length); });
Assert.Throws<ArgumentOutOfRangeException>(() => { outStream.WriteAsync(bytes, 0, bytes.Length + 1); });
}
[ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))]
public void InputCannotWriteAsync()
{
Stream inStream = Console.OpenStandardInput();
byte[] bytes = Encoding.ASCII.GetBytes("Hi");
Assert.Throws<NotSupportedException>(() => { inStream.WriteAsync(bytes, 0, bytes.Length); });
Assert.Throws<NotSupportedException>(() => { inStream.WriteAsync(bytes.AsMemory()); });
}
[Fact]
public void OutputCannotReadAsync()
{
Stream outStream = Console.OpenStandardOutput();
byte[] buffer = new byte[1024];
Assert.Throws<NotSupportedException>(() =>
{
outStream.ReadAsync(buffer, 0, buffer.Length);
});
Assert.Throws<NotSupportedException>(() =>
{
outStream.ReadAsync(buffer.AsMemory());
});
}
}
......@@ -6,8 +6,11 @@
using System.Text;
using Xunit;
class Helpers
static class Helpers
{
public static bool IsConsoleInSupported =>
!PlatformDetection.IsAndroid && !PlatformDetection.IsiOS && !PlatformDetection.IsMacCatalyst && !PlatformDetection.IstvOS && !PlatformDetection.IsBrowser;
public static void SetAndReadHelper(Action<TextWriter> setHelper, Func<TextWriter> getHelper, Func<StreamReader, string> readHelper)
{
const string TestString = "Test";
......
......@@ -30,13 +30,6 @@ public static void WriteOverloads()
}
}
[Fact]
public static void WriteToOutputStream_EmptyArray()
{
Stream outStream = Console.OpenStandardOutput();
outStream.Write(new byte[] { }, 0, 0);
}
[Fact]
[OuterLoop]
public static void WriteOverloadsToRealConsole()
......
......@@ -10,8 +10,7 @@
//
public class SetIn
{
[Fact]
[SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "Not supported on Browser, iOS, MacCatalyst, or tvOS.")]
[ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))]
public static void SetInThrowsOnNull()
{
TextReader savedIn = Console.In;
......@@ -25,8 +24,7 @@ public static void SetInThrowsOnNull()
}
}
[Fact]
[SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "Not supported on Browser, iOS, MacCatalyst, or tvOS.")]
[ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))]
public static void SetInReadLine()
{
const string TextStringFormat = "Test {0}";
......
......@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="CancelKeyPress.cs" />
<Compile Include="ConsoleStreamTests.cs" />
<Compile Include="Helpers.cs" />
<Compile Include="ReadAndWrite.cs" />
<Compile Include="ConsoleKeyInfoTests.cs" />
......@@ -35,8 +36,8 @@
</ItemGroup>
<ItemGroup>
<Content Include="$(MSBuildThisFileDirectory)TestData\**\*"
Link="%(RecursiveDir)%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest" />
Link="%(RecursiveDir)%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
<Compile Include="ConsoleEncoding.Windows.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册