异步ping

Gvs*_*Gvs 6 c# events asynchronous ping

陷入一个奇怪的"问题".有一个应用程序pinging整个网络.工作得很好,直到你到达一个255.255.0.0网络掩码的网络(这是65k +地址).

我发这样的话:

    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++;
            }
        }
Run Code Online (Sandbox Code Playgroud)

并像这样回忆他们:

    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
        }
    }
Run Code Online (Sandbox Code Playgroud)

问题是a)有时(很少)它没有完成(条件不符合).b)什么时候完成数字不匹配?如果我打印发送和收到刚才他们是

    Sent: 65025 Recieved: 64990
Run Code Online (Sandbox Code Playgroud)

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

And*_*own 7

你有任何锁定吗?这看起来像你的问题.我可以在您的代码中看到各种竞争条件和内存处理器缓存问题.

尝试lock用来保护recievedIpAddresses == sentPings

sentPings++;
//This counts pings being sent out
pingCounter++;
Run Code Online (Sandbox Code Playgroud)

运用 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的示例将强制从共享内存中读取和写入,以便不同的CPU内核不会读取不同的值.但是,根据您的代码,您可能需要更多的粗粒度锁定,其中第一个循环保护两个sentPingspingCounter一个lock,甚至第二个方法完全受到保护lock.

人们可以说不使用lock因为它会导致性能问题,而且免费锁定非常时髦.在lock大多数情况下,底线比其他替代方案更简单.您可能需要使锁定比上面的样本更粗糙,因为您可能也有竞争条件.如果没有看到整个程序,很难提供更好的样本.

Interlocked.Increment

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

比赛条件

(编辑)

即使你锁定你可能有问题.观看13个目标地址的时间表(不幸的是).如果你不熟悉这是为什么,那么看看"管理线程基础知识""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:
    • 除了一个物体,你不需要任何东西
    • 你无法锁定值类型
    • 你不应该锁定你的类实例(以防止更复杂的代码中的死锁)
    • 你不应该公开(也是为了防止死锁)
  • 它通过此语句与类(以线程安全方式)一起实例化
  • 它被称为SyncRoot一个例子; Visual Studio历来称之为它的片段

  • 或者使用Interlocked类来递增可以同时从多个线程访问的整数变量. (3认同)