遇到了一个奇怪的“问题”。拥有一个可以扫描整个网络的应用程序。在您进入具有 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
最佳答案
你有任何锁定吗?对我来说,这看起来像你的问题。我可以看到各种 race conditions and memory processor cache issues可能在您的代码中。
尝试使用 lock
用于保护 recievedIpAddresses == sentPings
和
sentPings++;
//This counts pings being sent out
pingCounter++;
使用锁
例如:
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
中同时保护 sentPings
和 pingCounter
,并且甚至第二种方法也可能完全受到 lock
的保护。
人们可以说不要使用 lock
因为它会导致性能问题,而 lock free 非常流行。底线是 lock
大多数时候比其他替代方案更简单。您可能需要使您的锁定比上面的示例更粗粒度,因为您也可能有竞争条件。如果没有看到您的整个程序,很难给出更好的示例。
互锁.Increment
这里使用 lock
的主要原因是强制每次读写都来自内存,而不是 CPU 缓存,因此您应该获得一致的值。锁定的替代方法是使用 Interlocked.Increment ,但如果您将它用于两个单独的变量,则需要仔细观察竞争条件。
竞争条件
(编辑)
即使您锁定了也可能会遇到问题。观看此时间线以了解 13 个目标地址(对某些人来说不走运)。如果您不明白这是为什么,请查看 "Managed Threading Basics"和 "Threading in C# - Joseph Albahari"
- T1:1 次
- T1:Ping 已发送
- T1:
sentPings++
- T2:1 次
- 收到IpAddresses++;
- T2:其他内容
- 同时 T1:12 次
- T1:Ping 已发送
- T1:
sentPings++
(现在等于 13)
- T2:
recievedIpAddresses == sentPings
测试 - 现在失败,因为它们不相等 - T3 到 T14:输入
pingCompleted
并执行recievedIpAddresses++;
- T1 完成,应用程序开始写出 ping 计数(或者更糟的是仍然完全退出),然后其他 12 个线程在后台返回
您需要仔细观察代码中的此类竞争条件,并相应地进行调整。关于线程的全部事情是它们重叠它们的操作。
同步根
脚注:
为什么 SyncRoot
声明为:private readonly object SyncRoot = new object();
?
- 它是一个保护类字段的类字段,如果你有一个
static
控制台应用程序,它需要是static
。但是,如果你在一个类中使用static
,那么每个实例都会锁定同一个对象,所以会有争用 - 声明意图是
只读
,防止您(或其他团队成员)稍后覆盖它 - 它是一个
对象
:- 除了一个对象你不需要任何东西
- 你不能锁定一个值类型
- 你不应该锁定你的类实例(以防止在更复杂的代码中出现deadlocks)
- 你不应该公开它(也是为了防止死锁)
- 它通过这个语句与类一起实例化(以线程安全的方式)
- 以
SyncRoot
为例; Visual Studio 历来在它的片段中这样调用它
关于c# - 异步ping,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16853746/