Mixing async-await with fire-and-forget approach

1.2k Views Asked by At

I'm writing a websocket server using .NET's HttpListener class.

Essentially, I've got a HandleListener() function which wait for clients to connect and yield each client to HandleClient(WebSocket client). So I currently have:

    private async void HandleListener()
    {
        try
        {
            while (listener != null && listener.IsListening)
            {
                HttpListenerContext listenerContext = await listener.GetContextAsync();
                WebSocketContext webSocketContext = await listenerContext.AcceptWebSocketAsync(subProtocol: null);
                WebSocket webSocket = webSocketContext.WebSocket;
                clients.Add(webSocket);
                await HandleClient(webSocket);
            }
        }
        catch (HttpListenerException) { } // Got here probably because StopWSServer() was called
    }

private async Task HandleClient(WebSocket client) { ... }

Problem is, I can't seem to process more then one client. It looks like the execution of HandleListener() halts as long as the first client is connected.

I tried removing the await from the call to HandleClient(), but I get the "because this call is not awaited..." error. I can make HandleClient() a async void method, but this is not an event handler.
BTW, the reason that HandleClient() is async Task is because it's doing, all over in a loop until the listener is dead:

recieveResult = await client.ReceiveAsync(recievedBuffer, CancellationToken.None);

From what I understand, a fire-and-forget approach is bad overall, and I can't seem to achieve it with async-await implementation. But HandleClient() is a fire-and-forget method, and I don't see any other way of achieving what I need.


EDIT: Added current implementation of HandleClient():

   private async Task HandleClient(WebSocket client)
    {
        try
        {
            ArraySegment<byte> recievedBuffer = new ArraySegment<byte>(new byte[BUFFER_SIZE]);
            while (listener != null && listener.IsListening && client.State == WebSocketState.Open)
            {
                WebSocketReceiveResult recieveResult;
                using (var ms = new MemoryStream())
                {
                    do
                    {
                        recieveResult = await client.ReceiveAsync(recievedBuffer, CancellationToken.None);
                        ms.Write(recievedBuffer.Array, recievedBuffer.Offset, recieveResult.Count);
                    }
                    while (!recieveResult.EndOfMessage);
                    switch (recieveResult.MessageType)
                    {
                        case WebSocketMessageType.Close:
                            RemoveClient(client, WebSocketCloseStatus.NormalClosure, string.Empty);
                            break;
                        case WebSocketMessageType.Binary:
                            RemoveClient(client, WebSocketCloseStatus.InvalidMessageType, "Cannot accept binary frame");
                            break;
                        case WebSocketMessageType.Text:
                            OnRecieve?.Invoke(client, System.Text.Encoding.UTF8.GetString(ms.ToArray()));
                            break;
                    }
                }
            }
        }
        catch (WebSocketException ex)
        {
            RemoveClient(client, WebSocketCloseStatus.InternalServerError, ex.Message);
        }
    }
2

There are 2 best solutions below

6
On

it will do like that because you wrote code for it, my mean to say you wrote method as below.

   private async void HandleListener()
    {
        try
        {
            while (listener != null && listener.IsListening)
            {
                HttpListenerContext listenerContext = await listener.GetContextAsync();
                WebSocketContext webSocketContext = await listenerContext.AcceptWebSocketAsync(subProtocol: null);
                WebSocket webSocket = webSocketContext.WebSocket;
                clients.Add(webSocket);
                await HandleClient(webSocket);
            }
        }
        catch (HttpListenerException) { } // Got here probably because StopWSServer() was called
    }

In this method when it encounter await control will get return to orignal caller ,till you await part got completed and next call start after it.

Check below image this how await and async works

enter image description here

If you just want fire and forget than try like this

   private void HandleListener()
    {
        try
        {
            while (listener != null && listener.IsListening)
            {
                HttpListenerContext listenerContext = await listener.GetContextAsync();
                WebSocketContext webSocketContext = await listenerContext.AcceptWebSocketAsync(subProtocol: null);
                WebSocket webSocket = webSocketContext.WebSocket;
                clients.Add(webSocket);
                HandleClient(webSocket);
            }
        }
        catch (HttpListenerException) { } // Got here probably because StopWSServer() was called
    }

which means dont wait for completion of task

0
On

To prevent compiler warning, use method like this:

public static class TaskExtensions {
    public static void Forget(this Task task) {

    }
}

then just do

HandleClient(webSocket).Forget()

If you go this route, ensure that you handle all exceptions inside HandleClient somehow (wrap whole thing into try-catch for example). There is nothing inherently "bad" in this approach in this particular case.

Alternative approach would be:

HandleClient(webSocket).ContinueWith(task => {
    if (task.IsFaulted && task.Exception != null) {
        // handle it here
    }
});

awaiting HandleClient is not an option in this case, as you see yourself.