混合async-await与fire-and-forget方法

时间:2017-11-30 09:15:22

标签: c# asynchronous async-await fire-and-forget

我正在使用.NET的HttpListener类编写websocket服务器。

基本上,我有一个HandleListener()函数,等待客户端连接并将每个客户端都生成HandleClient(WebSocket client)。所以我现在有:

    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) { ... }

问题是,我似乎无法处理多个客户端。只要第一个客户端连接,看起来HandleListener()的执行就会停止。

我尝试从调用await中移除HandleClient(),但我得到“因为此调用未等待...”错误。我可以使HandleClient()成为async void方法,但这不是事件处理程序 顺便说一下,HandleClient()async Task的原因是因为它正在循环,直到听众死了:

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

据我所知,一种即发即弃的方法总体上是糟糕的,我似乎无法用async-await实现它。但是HandleClient() 一种即发即弃的方法,我没有看到任何其他方法来实现我的需要。

编辑:添加了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 个答案:

答案 0 :(得分:1)

它会这样做,因为你为它编写代码,我的意思是说你写了如下方法。

   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
    }

在这种方法遇到await时,控制权将返回原始调用者,直到你等待部分完成,然后下一次调用开始。

检查下图,了解等待和异步的工作原理

enter image description here

如果你只是想要火而忘记而不是尝试这样

   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
    }

表示不等待任务完成

答案 1 :(得分:1)

要防止编译器警告,请使用以下方法:

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

    }
}

然后就这样做

HandleClient(webSocket).Forget()

如果你走这条路,确保以某种方式处理HandleClient内的所有异常(例如将整个事物包装到try-catch中)。在这种特殊情况下,这种方法没有任何固有的“坏”。

替代方法是:

HandleClient(webSocket).ContinueWith(task => {
    if (task.IsFaulted && task.Exception != null) {
        // handle it here
    }
});
等等HandleClient在这种情况下不是一种选择,就像你自己一样。