【问题标题】:Asynchronous pinging异步 ping
【发布时间】:2013-05-31 09:10:23
【问题描述】:

遇到了一个奇怪的“问题”。有一个可以对整个网络进行 ping 扫描的应用程序。在您连接到具有 255.255.0.0 网络掩码(即 65k + 地址)的网络之前效果很好。

我这样发送我的 ping:

    foreach (string str in ListContainingAddresses)
        {
            using (Ping ping = new Ping())
            {
                if (pingCounter == 10000) { Thread.Sleep(10000); pingCounter = 0; }
                //Make an eventhandler
                ping.PingCompleted += new PingCompletedEventHandler(pingCompleted);
                //Send the pings asynchronously
                ping.SendAsync(IPAddress.Parse(str), 1000);
                sentPings++;

                //This counts pings being sent out
                pingCounter++;
            }
        }

然后像这样接收它们:

    public void pingCompleted(object sender, PingCompletedEventArgs e)
    {
        //This counts recieved addresses 
        recievedIpAddresses++;

        if (e.Reply.Status == IPStatus.Success)
        {
            //Do something
        }
        else
        {
            /*Computer is down*/
        }
        //This checks if sent equals recieved
        if (recievedIpAddresses == sentPings )
        {
            //All returned
        }
    }

问题是 a) 有时(很少)它没有完成(条件不满足)。 b) 当它完成时,数字不匹配?如果我刚刚打印发送和接收它们是

    Sent: 65025 Recieved: 64990

尽管如此,满足条件并且应用程序继续运行?我不知道为什么以及如何发生这种情况。应用程序更新两个整数的代码是否执行得很快?是否有一些 ping 在途中丢失?如果我在具有 255 个地址的子网络上尝试它,则永远不会发生此问题。 不能使用 CountDownEvent 而不是变量,因为它的 .NET 3.5

【问题讨论】:

  • 我的钱会花在线程安全上。尽管它会使事情慢一点,但我会专注于使 pingCompleted 线程安全。
  • 愚蠢的问题:为什么不使用nmap?我敢打赌这将是最快的解决方案
  • 一点都不傻,我想自己学,就这么简单:)

标签: c# events asynchronous ping


【解决方案1】:

你有任何锁定吗?在我看来,这就像你的问题。我可以在您的代码中看到各种race conditions and memory processor cache issues

尝试使用lock 保护recievedIpAddresses == sentPings

sentPings++;
//This counts pings being sent out
pingCounter++;

使用lock

例如:

private readonly object SyncRoot = new object();

public void MainMethod()
{
    foreach (string str in ListContainingAddresses)
    { ... }
    lock (SyncRoot) { sentPings++; }
    ....
}

public void pingCompleted(object sender, PingCompletedEventArgs e)
{
    //This counts recieved addresses 
    lock (SyncRoot) { recievedIpAddresses++; } // lock this if it is used on other threads

    if (e.Reply.Status == IPStatus.Success)
    {
        //Do something
    }
    else
    {
        /*Computer is down*/
    }
    lock (SyncRoot) { // lock this to ensure reading the right value of sentPings
        //This checks if sent equals recieved
        if (recievedIpAddresses == sentPings )
        {
            //All returned
        }
    }
}

上面的示例将强制从共享内存读取和写入,这样不同的 CPU 内核就不会读取不同的值。但是,根据您的代码,您可能需要更粗粒度的锁定,其中第一个循环在一个lock 中同时保护sentPingspingCounter,甚至可能使用lock 完全保护第二种方法。

人们可以说不要使用lock,因为它会导致性能问题,并且无锁非常流行。底线是lock 在大多数情况下比其他替代方案更简单。您可能需要使锁定比上述示例更粗粒度,因为您也可能存在竞争条件。没有看到整个程序,很难给出更好的示例。

Interlocked.Increment

在这里使用lock 的主要原因是强制每次读取和写入都来自内存,而不是CPU 缓存,因此您应该获得一致的值。锁定的替代方法是使用Interlocked.Increment,但如果您在两个单独的变量上使用它,则需要仔细观察竞争条件。

比赛条件

(编辑)

即使您锁定了您也可能遇到问题。观看 13 个目标地址的时间线(有些人不走运)。如果您不明白这是为什么,请查看"Managed Threading Basics""Threading in C# - Joseph Albahari"

  • T1:1 次
    • T1:Ping 发送
    • T1:sentPings++
  • T2:1 次
    • recievedIpAddresses++;
    • T2:其他东西
  • 同时T1:12次
    • T1:Ping 发送
    • T1:sentPings++(现在等于 13)
  • T2:recievedIpAddresses == sentPings 测试 - 现在失败,因为它们不相等
  • T3 到 T14:输入 pingCompleted 并执行 recievedIpAddresses++;
  • T1 完成,应用程序在其他 12 个线程返回后台之前写出 ping 计数(或更糟的是完全退出)

您需要在代码中仔细观察这种类型的竞争条件,并进行相应的调整。线程的全部特点是它们的操作重叠。

同步根

脚注

为什么SyncRoot 声明为:private readonly object SyncRoot = new object();

  • 这是一个类字段,用于保护类字段,如果您有static 控制台应用程序,则需要为static。但是,如果你在一个类中使用static,那么每个实例都会锁定同一个对象,所以会有争用
  • readonly 声明意图,并防止您(或其他团队成员)稍后覆盖它
  • 这是一个object
    • 除了一个对象,你不需要任何东西
    • 您不能锁定值类型
    • 您不应锁定您的类实例(以防止deadlocks 出现更复杂的代码)
    • 您不应将其公开(也是为了防止死锁)
  • 通过该语句与类一起实例化(以线程安全的方式)
  • SyncRoot为例; Visual Studio 历来将其称为 sn-ps

【讨论】:

  • 其实没听说过lock,去研究一下。
  • 或者使用 Interlocked 类来增加可以同时从多个线程访问的整数变量。
  • @Gvs。为您添加了参考链接。德克,同意,但需要注意Interlocked 的竞争条件。粗粒度锁定可以更简单。
  • @Gvs。另外,我刚刚添加了一个编辑,显示如果您没有向我们展示的代码不能保护您免受竞争条件的影响,竞争条件会如何影响您。该编辑中有两个有价值的参考资料可以帮助您。
  • 感谢您的出色解释。但这使得我知道的很少:p。只需在计数器变量上添加 Console.Writeline 也会使事情变得混乱。如果我在满足完成条件后只在发送和接收时进行一次打印,那么一切正常。好吧,我想我有很多关于这个主题的阅读工作:)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-31
  • 1970-01-01
  • 2015-08-04
  • 1970-01-01
相关资源
最近更新 更多