.NET 6中ClientWebSocket.ReceiveAsync仅接收一次数据后无响应
问题:.NET 6中ClientWebSocket定时轮询仅能接收一次数据
在.NET 6环境下使用C#的System.Net.WebSockets.ClientWebSocket类,设置了16ms触发的定时器轮询WebSocket接收数据,收到数据包后反序列化为JSON并生成事件,但该逻辑仅能成功执行一次。
添加了测试打印语句:
Console.WriteLine("Awaiting started");
和
Console.WriteLine("Awaiting ended");
预期流程:轮询时打印"Awaiting started",完成接收后打印"Awaiting ended",非空数据包则执行反序列化。相关代码如下:
private async void poll(Object? sender, ElapsedEventArgs event_args) { switch (web_socket.State) { case WebSocketState.Open: { if (socket_connected == false) { socket_connected = true; } try { // ReceiveAsync automatically polls and receives the packet. Memory<Byte> memory = new Memory<Byte>(new Byte[1024]); Console.WriteLine("Awaiting started"); ValueWebSocketReceiveResult result = await web_socket.ReceiveAsync(memory, CancellationToken.None); Console.WriteLine("Awaiting ended"); // Only attempt deserialization if a packet was received, aka the buffer is not empty. if (result.Count != 0) { deserialize_event(memory.Slice(0, result.Count).ToArray()); } } catch (Exception exception) { Console.WriteLine("Pusher polling error: " + exception.ToString()); } } break; case WebSocketState.Closed: { connection_closed(web_socket.CloseStatus); } break; } }
实际现象:仅能接收一次数据包,仅打印一次"Awaiting ended",后续所有ReceiveAsync调用均无法返回。
问题原因
- 定时器与异步方法冲突:定时器触发的
poll是async void方法,第一次await ReceiveAsync会异步挂起,定时器再次触发时会并发调用poll,而ClientWebSocket的ReceiveAsync不支持并发操作,后续调用会被阻塞。 - ReceiveAsync特性:
ReceiveAsync会一直挂起等待数据,定时器16ms一次的触发会导致多次调用堆积,后续调用无法获取执行机会。
解决方法
方法一:用持续异步循环替代定时器
这是WebSocket接收的标准处理方式,能保证同一时间只有一个ReceiveAsync执行:
private async Task StartWebSocketReceiving() { while (web_socket.State == WebSocketState.Open) { try { Memory<byte> memory = new Memory<byte>(new byte[1024]); Console.WriteLine("Awaiting started"); ValueWebSocketReceiveResult result = await web_socket.ReceiveAsync(memory, CancellationToken.None); Console.WriteLine("Awaiting ended"); if (result.Count != 0) { deserialize_event(memory.Slice(0, result.Count).ToArray()); } // 处理关闭帧 if (result.MessageType == WebSocketMessageType.Close) { await web_socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); connection_closed(web_socket.CloseStatus); break; } } catch (Exception exception) { Console.WriteLine("Pusher polling error: " + exception.ToString()); break; } } }
在WebSocket连接成功后启动该任务:
// 连接成功后调用 _ = StartWebSocketReceiving();
方法二:添加并发控制保留定时器
如果必须用定时器,通过标志位保证同一时间只有一个接收操作在执行:
private bool _isReceiving = false; private async void poll(Object? sender, ElapsedEventArgs event_args) { if (_isReceiving) return; switch (web_socket.State) { case WebSocketState.Open: { if (socket_connected == false) { socket_connected = true; } try { _isReceiving = true; Memory<Byte> memory = new Memory<Byte>(new Byte[1024]); Console.WriteLine("Awaiting started"); ValueWebSocketReceiveResult result = await web_socket.ReceiveAsync(memory, CancellationToken.None); Console.WriteLine("Awaiting ended"); if (result.Count != 0) { deserialize_event(memory.Slice(0, result.Count).ToArray()); } if (result.MessageType == WebSocketMessageType.Close) { await web_socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); connection_closed(web_socket.CloseStatus); } } catch (Exception exception) { Console.WriteLine("Pusher polling error: " + exception.ToString()); } finally { _isReceiving = false; } } break; case WebSocketState.Closed: { connection_closed(web_socket.CloseStatus); } break; } }
关键说明
ClientWebSocket的ReceiveAsync不支持并发调用,必须保证同一时间只有一个接收操作在进行。- 异步循环比定时器更可靠,它会在每次接收完成后立即等待下一个数据,无需依赖固定间隔,能更高效处理WebSocket数据流。
内容的提问来源于stack exchange,提问作者FireCatMagic
相关产品推荐
相关产品推荐

