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

C# Parallel.For并行Ping遇无效主机名时循环终止问题求解

问题表现
  • 并行Ping多设备程序,目标地址传入IP时所有功能运行正常;传入主机名作为目标时,只要存在1个无法解析的主机名,后续所有Ping操作会直接停止。
  • 将逻辑包裹在try/catch块中始终无法捕获对应异常。
  • 复现规则:传入的地址集合全为IP时逻辑正常执行;集合内所有主机名均可解析时程序也可正常运行;只要集合中存在任意一个无效、无法解析的主机名,程序就无法正常执行完成。

说明:项目整体代码量较大,下文仅展示相关核心代码片段,若有遗漏必要信息可补充更新。

原有核心代码如下:

try
{
    Parallel.For(0, ipAddress.Count(), (i, loopState) =>
    {
        Ping ping = new Ping();
        int timeout = 2000;
        int highping = 300;

        PingReply pingReply = ping.Send(ipAddress[i].ToString(), timeout);


        this.BeginInvoke((Action)delegate ()
        {
            if (ipAddress[i] == primary4kIP.Text)
            {
                if (pingReply.Status == IPStatus.Success && pingReply.RoundtripTime >= highping)
                {
                    primary4kStatus.Image = Properties.Resources.high;
                }
                else if (pingReply.Status == IPStatus.Success && pingReply.RoundtripTime < highping)
                {
                    primary4kStatus.Image = Properties.Resources.up;
                }
                else
                {
                    primary4kStatus.Image = Properties.Resources.down;
                }
            }
            if (ipAddress[i] == backup4kIP.Text)
            {
                if (pingReply.Status == IPStatus.Success && pingReply.RoundtripTime >= highping)
                {
                    backup4kStatus.Image = Properties.Resources.high;
                }
                else if (pingReply.Status == IPStatus.Success && pingReply.RoundtripTime < highping)
                {
                    backup4kStatus.Image = Properties.Resources.up;
                }
                else
                {
                    backup4kStatus.Image = Properties.Resources.down;
                }
            }
            if (ipAddress[i] == backupdrab4kIP.Text)
            {
                if (pingReply.Status == IPStatus.Success && pingReply.RoundtripTime >= highping)
                {
                    backupdrab4kStatus.Image = Properties.Resources.high;
                }
                else if (pingReply.Status == IPStatus.Success && pingReply.RoundtripTime < highping)
                {
                    backupdrab4kStatus.Image = Properties.Resources.up;
                }
                else
                {
                    backupdrab4kStatus.Image = Properties.Resources.down;
                }
            }
            if (ipAddress[i] == esxi4kIP.Text)
            {
                if (pingReply.Status == IPStatus.Success && pingReply.RoundtripTime >= highping)
                {
                    esxi4kStatus.Image = Properties.Resources.high;
                }
                else if (pingReply.Status == IPStatus.Success && pingReply.RoundtripTime < highping)
                {
                    esxi4kStatus.Image = Properties.Resources.up;
                }
                else
                {
                    esxi4kStatus.Image = Properties.Resources.down;
                }
            }

        });

    });
}
catch
{

}
故障原因
  • 外层的try/catch仅能捕获Parallel.For调用所在线程抛出的异常,无法直接捕获并行工作线程上迭代内部抛出的异常。
  • 当传入无法解析的主机名时,ping.Send()方法会在DNS解析阶段抛出PingException,该异常发生在并行工作线程的单个迭代中。Parallel类的运行规则是:只要任意一个迭代抛出未处理异常,就会立即触发循环终止,停止所有未执行的迭代,这就是单个无效主机名导致所有后续Ping停止的根本原因。
  • 原有代码的空catch块既没有正确捕获并行循环包装抛出的AggregateException,也没有做单迭代的异常隔离,自然无法捕获异常、也阻止不了循环终止。
  • 原有代码存在额外隐患:如果ping.Send()抛出异常,pingReply变量未被正确赋值,后续UI线程执行委托访问pingReply.Status时会触发空引用异常。同时Ping类为IDisposable实现,用完未释放会造成资源泄漏。
修复方案
  1. 异常隔离下放到单个并行迭代内部,每个迭代的Ping操作自己捕获异常,不允许异常逃逸出迭代委托,避免单个地址故障终止整个并行循环。
  2. 统一处理Ping成功、失败、解析异常的状态映射,减少冗余代码。
  3. 用完Ping对象及时释放,避免资源泄漏。

修复后的核心代码:

Parallel.For(0, ipAddress.Count(), (i, loopState) =>
{
    PingReply pingReply = null;
    Ping ping = null;
    try
    {
        ping = new Ping();
        int timeout = 2000;
        pingReply = ping.Send(ipAddress[i].ToString(), timeout);
    }
    catch (PingException)
    {
        // 捕获DNS解析失败、ICMP发送失败异常,pingReply保持null即可
    }
    finally
    {
        ping?.Dispose(); // 无论成功失败都释放Ping资源
    }

    this.BeginInvoke((Action)delegate ()
    {
        // 统一计算状态图标,减少重复判断
        Image targetStatus = Properties.Resources.down;
        if (pingReply != null && pingReply.Status == IPStatus.Success)
        {
            targetStatus = pingReply.RoundtripTime >= 300 
                ? Properties.Resources.high 
                : Properties.Resources.up;
        }

        // 匹配对应地址更新状态
        if (ipAddress[i] == primary4kIP.Text) primary4kStatus.Image = targetStatus;
        if (ipAddress[i] == backup4kIP.Text) backup4kStatus.Image = targetStatus;
        if (ipAddress[i] == backupdrab4kIP.Text) backupdrab4kStatus.Image = targetStatus;
        if (ipAddress[i] == esxi4kIP.Text) esxi4kStatus.Image = targetStatus;
    });
});
补充说明
  • 原有包裹整个Parallel.For的空catch块可以直接移除,没有实际作用。
  • 并行循环的异常隔离是必须的,只要有一个迭代的未处理异常抛到Parallel层面,整个循环就会被终止,不存在跳过错误项继续执行的默认逻辑。

内容的提问来源于stack exchange,提问作者dwb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 21:27:20