异步ping

时间:2013-05-31 09:10:24

标签: c# events asynchronous ping

陷入一个奇怪的“问题”。有一个应用程序pinging整个网络。在你使用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

尽管如此,条件得到满足并且申请仍在继续?我不知道为什么以及如何发生这种情况。代码是否快速执行,以便应用程序更新两个整数?沿途会有一些丢失吗?如果我在具有255个地址的子网上尝试它,这个问题永远不会发生。 自.NET 3.5

以来,不能使用CountDownEvent而不是变量

1 个答案:

答案 0 :(得分:7)

你有任何锁定吗?这看起来像你的问题。我可以在您的代码中看到各种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内核不会读取不同的值。但是,根据您的代码,您可能需要更多粗粒度锁定,其中第一个循环保护sentPings中的pingCounterlock,甚至第二个方法完全受到保护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上

脚注

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

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