如何停止处于阻塞监听状态的UdpClient实例?
如何停止阻塞的UdpClient/Socket监听?
针对你遇到的阻塞式UDP监听无法通过CancellationToken停止的问题,这里提供几种实用的解决方案:
方法1:通过关闭UdpClient触发异常终止监听
阻塞状态的UdpClient.Receive()或ReceiveAsync()会在调用Close()时抛出SocketException,我们可以捕获该异常来优雅终止监听任务,同时配合标记变量避免重复操作。
示例代码:
private UdpClient _udpClient; private bool _isStopping; // 启动UDP监听任务 void StartUdpListening(IPAddress localAddress) { _isStopping = false; _udpClient = new UdpClient(new IPEndPoint(localAddress, 12345)); Task.Run(async () => { try { while (!_isStopping) { // 异步接收(同步Receive逻辑一致) var receiveResult = await _udpClient.ReceiveAsync(); // 处理接收到的数据 // ProcessReceivedData(receiveResult.Buffer, receiveResult.RemoteEndPoint); } } catch (SocketException ex) { // 仅处理非预期异常,关闭导致的中断异常(错误码10004)无需处理 if (ex.SocketErrorCode != SocketError.Interrupted) { // 记录或处理其他网络异常 } } finally { _udpClient?.Dispose(); } }); } // 网络变化时停止监听 private void OnNetworkAddressChanged(object sender, EventArgs e) { _isStopping = true; _udpClient?.Close(); // 关闭客户端触发阻塞接收的异常,退出循环 }
方法2:使用Socket.Poll实现轮询式监听
直接使用底层Socket,通过Poll()方法定期检查是否有数据到达,同时轮询取消标记,避免完全阻塞。
示例代码:
private Socket _udpSocket; private CancellationTokenSource _cts; // 启动监听 void StartSocketListening(IPAddress localAddress) { _cts = new CancellationTokenSource(); _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); _udpSocket.Bind(new IPEndPoint(localAddress, 12345)); Task.Run(() => { byte[] buffer = new byte[1024]; try { while (!_cts.Token.IsCancellationRequested) { // 每隔100ms检查一次是否有数据可读,同时响应取消标记 if (_udpSocket.Poll(100000, SelectMode.SelectRead)) { int receivedBytes = _udpSocket.Receive(buffer); // 处理数据 // ProcessReceivedData(buffer, receivedBytes); } } } finally { _udpSocket?.Close(); _cts?.Dispose(); } }); } // 网络变化时取消监听 private void OnNetworkAddressChanged(object sender, EventArgs e) { _cts.Cancel(); }
方法3:异步接收+取消令牌回调
对于支持.NET 5及以上版本的环境,UdpClient.ReceiveAsync()可直接传入CancellationToken,同时注册取消回调确保客户端被关闭:
private UdpClient _udpClient; private CancellationTokenSource _cts; void StartAsyncUdpListening(IPAddress localAddress) { _cts = new CancellationTokenSource(); _udpClient = new UdpClient(new IPEndPoint(localAddress, 12345)); // 注册取消回调:当令牌触发时关闭客户端 _cts.Token.Register(() => _udpClient.Close()); Task.Run(async () => { try { while (!_cts.Token.IsCancellationRequested) { var receiveResult = await _udpClient.ReceiveAsync(_cts.Token); // 处理数据 } } catch (OperationCanceledException) { // 正常取消,无需额外处理 } catch (SocketException ex) { if (ex.SocketErrorCode != SocketError.Interrupted) { // 处理其他异常 } } finally { _udpClient?.Dispose(); _cts?.Dispose(); } }); } // 网络变化时取消 private void OnNetworkAddressChanged(object sender, EventArgs e) { _cts.Cancel(); }
注意事项
- 所有操作需保证线程安全,修改监听状态或操作客户端实例时,可通过锁或原子变量避免冲突。
- 客户端关闭后务必调用
Dispose()释放资源,防止内存泄漏。 - 异常捕获时需区分"主动关闭导致的中断"和"其他网络错误",避免误处理正常终止逻辑。
内容的提问来源于stack exchange,提问作者user20138168
相关产品推荐
相关产品推荐

