diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs index 7ee385c717373..a30e4abe46ad6 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs @@ -431,7 +431,7 @@ public override Task ReadAsync(byte[] array, int offset, int count, Cancell return Task.FromResult(0); // return immediately if no bytes requested; no need for overhead. Memory buffer = new Memory(array, offset, count); - SerialStreamIORequest result = new SerialStreamIORequest(cancellationToken, buffer); + SerialStreamReadRequest result = new SerialStreamReadRequest(cancellationToken, buffer); _readQueue.Enqueue(result); EnsureIOLoopRunning(); @@ -439,6 +439,23 @@ public override Task ReadAsync(byte[] array, int offset, int count, Cancell return result.Task; } +#if !NETFRAMEWORK && !NETSTANDARD2_0 + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + CheckHandle(); + + if (buffer.IsEmpty) + return new ValueTask(0); + + SerialStreamReadRequest result = new SerialStreamReadRequest(cancellationToken, buffer); + _readQueue.Enqueue(result); + + EnsureIOLoopRunning(); + + return new ValueTask(result.Task); + } +#endif + public override Task WriteAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) { CheckWriteArguments(array, offset, count); @@ -446,8 +463,8 @@ public override Task WriteAsync(byte[] array, int offset, int count, Cancellatio if (count == 0) return Task.CompletedTask; // return immediately if no bytes to write; no need for overhead. - Memory buffer = new Memory(array, offset, count); - SerialStreamIORequest result = new SerialStreamIORequest(cancellationToken, buffer); + ReadOnlyMemory buffer = new ReadOnlyMemory(array, offset, count); + SerialStreamWriteRequest result = new SerialStreamWriteRequest(cancellationToken, buffer); _writeQueue.Enqueue(result); EnsureIOLoopRunning(); @@ -455,6 +472,23 @@ public override Task WriteAsync(byte[] array, int offset, int count, Cancellatio return result.Task; } +#if !NETFRAMEWORK && !NETSTANDARD2_0 + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + CheckWriteArguments(); + + if (buffer.IsEmpty) + return ValueTask.CompletedTask; // return immediately if no bytes to write; no need for overhead. + + SerialStreamWriteRequest result = new SerialStreamWriteRequest(cancellationToken, buffer); + _writeQueue.Enqueue(result); + + EnsureIOLoopRunning(); + + return new ValueTask(result.Task); + } +#endif + public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, object stateObject) { return TaskToApm.Begin(ReadAsync(array, offset, numBytes), userCallback, stateObject); @@ -715,7 +749,8 @@ private void RaiseDataReceivedEof() private unsafe int ProcessRead(SerialStreamIORequest r) { - Span buff = r.Buffer.Span; + SerialStreamReadRequest readRequest = (SerialStreamReadRequest)r; + Span buff = readRequest.Buffer.Span; fixed (byte* bufPtr = buff) { // assumes dequeue-ing happens on a single thread @@ -728,12 +763,12 @@ private unsafe int ProcessRead(SerialStreamIORequest r) // ignore EWOULDBLOCK since we handle timeout elsewhere if (lastError.Error != Interop.Error.EWOULDBLOCK) { - r.Complete(Interop.GetIOException(lastError)); + readRequest.Complete(Interop.GetIOException(lastError)); } } else if (numBytes > 0) { - r.Complete(numBytes); + readRequest.Complete(numBytes); return numBytes; } else // numBytes == 0 @@ -747,7 +782,8 @@ private unsafe int ProcessRead(SerialStreamIORequest r) private unsafe int ProcessWrite(SerialStreamIORequest r) { - ReadOnlySpan buff = r.Buffer.Span; + SerialStreamWriteRequest writeRequest = (SerialStreamWriteRequest)r; + ReadOnlySpan buff = writeRequest.Buffer.Span; fixed (byte* bufPtr = buff) { // assumes dequeue-ing happens on a single thread @@ -766,11 +802,11 @@ private unsafe int ProcessWrite(SerialStreamIORequest r) } else { - r.ProcessBytes(numBytes); + writeRequest.ProcessBytes(numBytes); - if (r.Buffer.Length == 0) + if (writeRequest.Buffer.Length == 0) { - r.Complete(); + writeRequest.Complete(); } return numBytes; @@ -962,35 +998,57 @@ private static Exception GetLastIOError() return Interop.GetIOException(Interop.Sys.GetLastErrorInfo()); } - private sealed class SerialStreamIORequest : TaskCompletionSource + private abstract class SerialStreamIORequest : TaskCompletionSource { - public Memory Buffer { get; private set; } public bool IsCompleted => Task.IsCompleted; private CancellationToken _cancellationToken; + private readonly CancellationTokenRegistration _cancellationTokenRegistration; - public SerialStreamIORequest(CancellationToken ct, Memory buffer) + protected SerialStreamIORequest(CancellationToken ct) : base(TaskCreationOptions.RunContinuationsAsynchronously) { _cancellationToken = ct; - ct.Register(s => ((TaskCompletionSource)s).TrySetCanceled(), this); - - Buffer = buffer; - } - - internal void Complete() - { - Debug.Assert(Buffer.Length == 0); - TrySetResult(Buffer.Length); + _cancellationTokenRegistration = ct.Register(s => ((TaskCompletionSource)s).TrySetCanceled(), this); } internal void Complete(int numBytes) { TrySetResult(numBytes); + _cancellationTokenRegistration.Dispose(); } internal void Complete(Exception exception) { TrySetException(exception); + _cancellationTokenRegistration.Dispose(); + } + } + + private sealed class SerialStreamReadRequest : SerialStreamIORequest + { + public Memory Buffer { get; private set; } + + public SerialStreamReadRequest(CancellationToken ct, Memory buffer) + : base(ct) + { + Buffer = buffer; + } + } + + private sealed class SerialStreamWriteRequest : SerialStreamIORequest + { + public ReadOnlyMemory Buffer { get; private set; } + + public SerialStreamWriteRequest(CancellationToken ct, ReadOnlyMemory buffer) + : base(ct) + { + Buffer = buffer; + } + + internal void Complete() + { + Debug.Assert(Buffer.Length == 0); + Complete(Buffer.Length); } internal void ProcessBytes(int numBytes) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.cs index 6a8505c7ba1ae..2de7ab2d15b35 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.cs @@ -10,9 +10,6 @@ namespace System.IO.Ports { -// Issue https://github.com/dotnet/runtime/issues/54916 -// 'SerialStream' overrides array-based 'ReadAsync' but does not override memory-based 'ReadAsync'. Consider overriding memory-based 'ReadAsync' to improve performance. -// 'SerialStream' overrides array-based 'WriteAsync' but does not override memory-based 'WriteAsync'. Consider overriding memory-based 'WriteAsync' to improve performance. #pragma warning disable CA1844 internal sealed partial class SerialStream : Stream #pragma warning restore CA1844 @@ -91,7 +88,7 @@ public override void Write(byte[] array, int offset, int count) Dispose(false); } - private void CheckReadWriteArguments(byte[] array, int offset, int count) + private void CheckArrayArguments(byte[] array, int offset, int count) { if (array == null) throw new ArgumentNullException(nameof(array)); @@ -101,10 +98,29 @@ private void CheckReadWriteArguments(byte[] array, int offset, int count) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNumRequired); if (array.Length - offset < count) throw new ArgumentException(SR.Argument_InvalidOffLen); + } + + private void CheckHandle() + { if (_handle == null) InternalResources.FileNotOpen(); } + private void CheckReadWriteArguments(byte[] array, int offset, int count) + { + CheckArrayArguments(array, offset, count); + + CheckHandle(); + } + + private void CheckWriteArguments() + { + if (_inBreak) + throw new InvalidOperationException(SR.In_Break_State); + + CheckHandle(); + } + private void CheckWriteArguments(byte[] array, int offset, int count) { if (_inBreak)