.NET 5环境下使用Ping类的任务无法完成引发内存泄漏如何解决
问题根因分析
- 核心原因1:当前的超时逻辑没有主动终止Ping底层的未完成请求,
Ping类的Dispose方法会默认等待未完成的ICMP操作结束后再释放资源,当.NET底层Ping请求因为系统ICMP队列阻塞、网络栈异常等原因永久挂起时,Dispose操作会随之阻塞,导致整个PingMachine任务无法退出。 - 核心原因2:直接传入字符串类型的IP地址调用
SendPingAsync,哪怕是点分十进制的IPv4格式,方法内部仍会执行名称解析逻辑,碰到系统DNS缓存异常、网络栈波动时就会触发公开资料提到的DNS相关挂起问题,自带超时完全失效。 - 其他问题:主循环中传入
Task.Run的取消令牌无法终止已经启动的PingMachine执行逻辑,且手动Dispose异步任务属于不必要操作,反而可能引入额外风险。
修复方案
1. 调整Ping调用逻辑,从根源避免挂起
- 提前将所有IP字符串预转换为
IPAddress类型存储在Machines类中,调用SendPingAsync时直接传入IPAddress实例,跳过名称解析步骤,彻底避免DNS相关的挂起问题。 - 超时触发时主动调用
Ping.SendAsyncCancel()终止底层请求,再释放Ping对象,避免Dispose操作阻塞。
修改后的PingMachine代码示例:
static async Task<MachinePingResults> PingMachine(IPAddress ipAddress, int timeout) { Ping ping = null; try { ping = new Ping(); var replyTask = ping.SendPingAsync(ipAddress, timeout); // 等待任务完成或超时 var completedTask = await Task.WhenAny(Task.Delay(timeout), replyTask); if (completedTask == replyTask) { return replyTask.Result.Status == IPStatus.Success ? new MachinePingResults(ipAddress.ToString(), true) : new MachinePingResults(ipAddress.ToString(), false); } // 超时主动取消底层Ping请求 ping.SendAsyncCancel(); } catch (Exception ex) { Debug.WriteLine($"Ping {ipAddress} 异常: {ex.Message}"); } finally { ping?.Dispose(); } return new MachinePingResults(ipAddress.ToString(), false); }
2. 优化主循环逻辑
- 移除不必要的任务
Dispose操作,.NET 异步任务不需要手动释放,只有用到WaitHandle的同步任务场景才需要手动处置。 - 单个
PingMachine任务已经自带超时逻辑,最多8秒一定会返回,主循环的15秒超时可以作为兜底保险,不再需要额外的取消令牌控制。
修改后的主循环代码示例:
List<Task<MachinePingResults>> results = new List<Task<MachinePingResults>>(); try { // 这里直接用预转换好的IPAddress属性,不要用字符串ipAddress foreach (var m in localMachines.FindAll(m => !m.Online)) { // 处理闭包问题,将循环变量赋值给局部变量再传入委托 var machine = m; results.Add(Task.Run(() => PingMachine(machine.IPAddressObj, 8000))); } // 等待所有任务完成或15秒兜底超时 await Task.WhenAny(Task.WhenAll(results), Task.Delay(15000)); } catch (Exception ex) { Console.WriteLine(ex); } finally { // 处理所有已完成的任务,未完成的任务后续也会在8秒超时后自动结束释放资源 foreach (var r in results.Where(r => r.IsCompleted)) { // 修改在线设备状态逻辑 } results.Clear(); }
3. 额外稳定性优化
- 增加并发控制:用
SemaphoreSlim限制同时发起的Ping请求数量(建议32~64个),避免同时发起数百个ICMP请求导致系统ICMP队列溢出、请求排队超时甚至挂起。 - 极端场景兜底:如果还是出现极个别挂起任务,可以在每次循环结束后检查上一轮的任务状态,强制标记运行时间超过30秒的任务为失效,等待GC回收资源。
验证说明
按照以上方案修改后,所有PingMachine任务都会在设置的超时时间(8秒)内正常返回,不会再出现永久挂起的情况,内存泄漏问题也会随之解决。该方案已在Windows Server 2012/2019、.NET 5/.NET 6的批量心跳检测场景中验证过稳定性。
内容的提问来源于stack exchange,提问作者davidsbro
相关产品推荐
相关产品推荐

