在.NET Framework中如何用闭包实现System.Net.WebSockets.ClientWebSocket事件化消息处理?
你的理解完全正确!
没错,System.Net.WebSockets.ClientWebSocket确实没有提供你在macOS/iOS开发中习惯的事件驱动式API,它采用的是需要主动、持续调用ReadAsync方法来拉取消息的传统模式——这是它作为底层WebSocket客户端实现的设计特点,目的是给开发者最大的灵活性来控制线程、循环逻辑和资源管理。
要把它改造成你熟悉的“消息到达时自动触发代码块”的形式,我们可以自己封装一层,把主动拉取的逻辑包装成事件驱动的模型,同时适配你习惯的函数式编程风格。下面是具体的实现思路和代码示例:
1. 封装一个事件驱动的WebSocket客户端类
我们可以创建一个自定义类,内部管理ClientWebSocket的生命周期,在后台线程中运行消息读取循环,当收到消息时触发自定义事件(或者直接调用你传入的回调函数)。
using System; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; public class EventDrivenWebSocket : IDisposable { private ClientWebSocket _webSocket; private CancellationTokenSource _cts; private bool _isDisposed; // 定义消息接收事件,符合.NET事件规范 public event EventHandler<string> MessageReceived; // 定义连接断开事件 public event EventHandler Disconnected; // 也可以提供函数式的回调注册方式,贴近你习惯的闭包用法 public void OnMessageReceived(Action<string> callback) { MessageReceived += (sender, message) => callback(message); } public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken = default) { _webSocket = new ClientWebSocket(); _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); await _webSocket.ConnectAsync(uri, _cts.Token); // 启动后台消息监听循环 _ = Task.Run(ListenForMessagesAsync, _cts.Token); } public async Task SendMessageAsync(string message, CancellationToken cancellationToken = default) { if (_webSocket.State != WebSocketState.Open) throw new InvalidOperationException("WebSocket is not open."); var buffer = Encoding.UTF8.GetBytes(message); var segment = new ArraySegment<byte>(buffer); await _webSocket.SendAsync(segment, WebSocketMessageType.Text, true, cancellationToken); } private async Task ListenForMessagesAsync() { var buffer = new byte[1024 * 4]; try { while (_webSocket.State == WebSocketState.Open && !_cts.IsCancellationRequested) { var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token); if (result.MessageType == WebSocketMessageType.Close) { await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); break; } var message = Encoding.UTF8.GetString(buffer, 0, result.Count); // 触发消息接收事件 MessageReceived?.Invoke(this, message); } } catch (OperationCanceledException) { // 预期的取消操作,无需处理 } catch (Exception ex) { // 这里可以添加异常处理逻辑,比如触发错误事件 Console.WriteLine($"WebSocket error: {ex.Message}"); } finally { // 触发断开连接事件 Disconnected?.Invoke(this, EventArgs.Empty); Dispose(); } } public void Dispose() { if (_isDisposed) return; _cts?.Cancel(); _webSocket?.Dispose(); _cts?.Dispose(); _isDisposed = true; } }
2. 使用封装后的客户端(两种方式)
方式1:使用.NET事件模式
var wsClient = new EventDrivenWebSocket(); // 订阅消息接收事件 wsClient.MessageReceived += (sender, message) => { // 这里就是你要处理消息的代码块,相当于闭包 Console.WriteLine($"Received message: {message}"); // 可以在这里把消息转发到其他业务逻辑模块 }; // 订阅断开事件 wsClient.Disconnected += (sender, args) => { Console.WriteLine("WebSocket disconnected."); }; // 连接到服务器 await wsClient.ConnectAsync(new Uri("wss://your-private-push-server.com")); // 发送测试消息 await wsClient.SendMessageAsync("Hello from event-driven client!"); // 保持程序运行(实际项目中根据你的应用类型调整,比如WPF/WinForms的消息循环) Console.ReadLine(); // 清理资源 wsClient.Dispose();
方式2:使用函数式回调(更贴近你习惯的闭包风格)
var wsClient = new EventDrivenWebSocket(); // 直接传入回调函数(闭包) wsClient.OnMessageReceived(message => { Console.WriteLine($"Received via callback: {message}"); // 在这里处理消息,比如更新UI、触发业务逻辑等 }); await wsClient.ConnectAsync(new Uri("wss://your-private-push-server.com")); // ... 后续逻辑同上
关键注意事项
- 线程安全:如果你的回调/事件处理逻辑涉及UI操作(比如WinForms/WPF),需要切换到UI线程执行(比如使用
Dispatcher.Invoke或Control.Invoke),因为消息监听循环运行在后台线程。 - 异常处理:封装类中已经做了基础的异常捕获,你可以根据需求扩展错误事件,或者在回调中处理特定业务异常。
- 资源管理:一定要调用
Dispose方法清理WebSocket和取消令牌资源,避免内存泄漏。
这种封装方式既保留了ClientWebSocket的底层灵活性,又给你提供了熟悉的事件/函数式编程体验,完美适配你的私有推送通知服务需求。
内容的提问来源于stack exchange,提问作者ygini




