关于C#:使用即发即弃方法混合异步等待

Mixing async-await with fire-and-forget approach

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

本质上,我有一个HandleListener()函数,该函数等待客户端连接并将每个客户端转换为HandleClient(WebSocket client)。 所以我目前有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    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()就会暂停执行。

我尝试从对HandleClient()的调用中删除await,但出现"因为未等待此调用..."错误。 我可以将HandleClient()设置为async void方法,但这不是事件处理程序。
顺便说一句,HandleClient()async Task的原因是因为它一直在循环执行,直到侦听器死机为止:

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

据我了解,一劳永逸的做法总体上是不好的,而且我似乎无法通过async-await实现来实现。 但是HandleClient()是一劳永逸的方法,而且我看不到任何其他实现我所需要的方法的方法。

编辑:添加了HandleClient()的当前实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
   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);
        }
    }


为了防止编译器警告,请使用如下方法:

1
2
3
4
5
public static class TaskExtensions {
    public static void Forget(this Task task) {

    }
}

然后做

1
HandleClient(webSocket).Forget()

如果走这条路,请确保以某种方式处理HandleClient内部的所有异常(例如,将整个内容包装到try-catch中)。 在这种情况下,这种方法没有内在的"坏"之处。

替代方法是:

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

如您所见,在这种情况下,等待HandleClient并不是一种选择。


之所以会这样,是因为您为此编写了代码,我的意思是说您编写了如下方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   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

如果你只是想生火而忘了尝试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   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
    }

这意味着不要等待任务完成