简体   繁体   中英

Mixing async-await with fire-and-forget approach

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);
        }
    }

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

在此处输入图片说明

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM