You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 08:45:05