Skip to content

Commit 269dfdc

Browse files
committed
Switch to ValueTask for async methods
We only have CompleteAsync and RunAsync on IWebSocketPipe, but since the underlying primitives we invoke on either the pipe reader/writer or websocket, all return ValueTask to avoid allocations, we might as well follow suit. Background: https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/ Fixes #3
1 parent 1f20632 commit 269dfdc

File tree

4 files changed

+17
-13
lines changed

4 files changed

+17
-13
lines changed

src/Tests/SimpleWebSocketPipeTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public record SimpleWebSocketPipeTests(ITestOutputHelper Output)
1212
public async Task WhenWebSocketNotOpen_ThenThrowsAsync()
1313
{
1414
IWebSocketPipe pipe = WebSocketPipe.Create(new ClientWebSocket());
15-
await Assert.ThrowsAsync<InvalidOperationException>(() => pipe.RunAsync());
15+
await Assert.ThrowsAsync<InvalidOperationException>(() => pipe.RunAsync().AsTask());
1616
}
1717

1818
[Fact]
@@ -26,7 +26,7 @@ public async Task WhenConnected_ThenRuns()
2626
using var pipe = WebSocketPipe.Create(socket);
2727

2828
await Task.WhenAll(
29-
pipe.RunAsync(server.Cancellation.Token),
29+
pipe.RunAsync(server.Cancellation.Token).AsTask(),
3030
Task.Delay(100).ContinueWith(_ => server.Cancellation.Cancel()));
3131
}
3232

@@ -41,7 +41,9 @@ public async Task WhenServerClosesWebSocket_ThenClientCompletesGracefully()
4141

4242
await server.DisposeAsync();
4343

44-
Task.WaitAny(run, Task.Delay(100).ContinueWith(_ => throw new TimeoutException()));
44+
Task.WaitAny(
45+
run.AsTask(),
46+
Task.Delay(100).ContinueWith(_ => throw new TimeoutException()));
4547
}
4648

4749
[Fact]

src/Tests/WebSocketServer.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.IO.Pipelines;
2-
using System.Net;
1+
using System.Net;
32
using System.Net.WebSockets;
43
using Xunit.Abstractions;
54

@@ -52,7 +51,7 @@ static WebSocketServer Create(Func<IWebSocketPipe, Task>? pipeBehavior, Func<Web
5251
if (pipeBehavior != null)
5352
{
5453
using var pipe = WebSocketPipe.Create(websocket, options);
55-
await Task.WhenAll(pipeBehavior(pipe), pipe.RunAsync(cts.Token));
54+
await Task.WhenAll(pipeBehavior(pipe), pipe.RunAsync(cts.Token).AsTask());
5655
}
5756
else if (socketBehavior != null)
5857
{

src/WebSocketPipe/IWebSocketPipe.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public interface IWebSocketPipe : IDuplexPipe, IDisposable
4141
/// <param name="closeStatusDescription">Optional close status description to use if the underlying
4242
/// <see cref="WebSocket"/> is closed.</param>
4343
/// <returns></returns>
44-
public Task CompleteAsync(WebSocketCloseStatus? closeStatus = null, string? closeStatusDescription = null);
44+
public ValueTask CompleteAsync(WebSocketCloseStatus? closeStatus = null, string? closeStatusDescription = null);
4545

4646
/// <summary>
4747
/// Starts populating the <see cref="IDuplexPipe.Input"/> with incoming data from the underlying
@@ -54,6 +54,6 @@ public interface IWebSocketPipe : IDuplexPipe, IDisposable
5454
/// <see cref="IDuplexPipe.Output"/> are completed, or an explicit invocation of <see cref="CompleteAsync"/>
5555
/// is executed.
5656
/// </returns>
57-
public Task RunAsync(CancellationToken cancellation = default);
57+
public ValueTask RunAsync(CancellationToken cancellation = default);
5858
}
5959
}

src/WebSocketPipe/SimpleWebSocketPipe.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public SimpleWebSocketPipe(WebSocket webSocket, WebSocketPipeOptions options)
4141

4242
public string? SubProtocol => webSocket.SubProtocol;
4343

44-
public async Task RunAsync(CancellationToken cancellation = default)
44+
public async ValueTask RunAsync(CancellationToken cancellation = default)
4545
{
4646
if (webSocket.State != WebSocketState.Open)
4747
throw new InvalidOperationException($"WebSocket must be opened. State was {webSocket.State}");
@@ -51,10 +51,13 @@ public async Task RunAsync(CancellationToken cancellation = default)
5151

5252
// NOTE: when both are completed, the CompleteAsync will be called automatically
5353
// by both writing and reading, so we ensure CloseWhenCompleted is performed.
54-
await Task.WhenAll(reading, writing);
54+
55+
// TODO: replace with ValueTask.WhenAll if/when it ships.
56+
// See https://github.com/dotnet/runtime/issues/23625
57+
await Task.WhenAll(reading.AsTask(), writing.AsTask());
5558
}
5659

57-
public async Task CompleteAsync(WebSocketCloseStatus? closeStatus = null, string? closeStatusDescription = null)
60+
public async ValueTask CompleteAsync(WebSocketCloseStatus? closeStatus = null, string? closeStatusDescription = null)
5861
{
5962
if (completed)
6063
return;
@@ -87,7 +90,7 @@ async ValueTask CloseAsync(WebSocketCloseStatus closeStatus, string closeStatusD
8790
await Task.WhenAny(closeTask, Task.Delay(closeTimeout));
8891
}
8992

90-
async Task FillInputAsync(CancellationToken cancellation)
93+
async ValueTask FillInputAsync(CancellationToken cancellation)
9194
{
9295
while (webSocket.State == WebSocketState.Open && !cancellation.IsCancellationRequested)
9396
{
@@ -126,7 +129,7 @@ ex is WebSocketException ||
126129
await CompleteAsync(webSocket.CloseStatus, webSocket.CloseStatusDescription);
127130
}
128131

129-
async Task SendOutputAsync(CancellationToken cancellation)
132+
async ValueTask SendOutputAsync(CancellationToken cancellation)
130133
{
131134
while (webSocket.State == WebSocketState.Open && !cancellation.IsCancellationRequested)
132135
{

0 commit comments

Comments
 (0)