Ping 任务将无法完成
Ping Tasks will not complete
我正在开发一个“心跳”应用程序,它每分钟通过循环 ping 数百个 IP 地址。 IP 地址存储在 class Machines
的列表中。我有一个循环,它为每个 IP 创建一个 Task<MachinePingResults>
(其中 MachinePingResults
基本上是一个 IP 和在线状态的元组)并使用 System.Net.NetworkInformation
.
调用 ping 函数
我遇到的问题是,在 运行 数小时(或数天)之后,主程序的一个循环未能完成 Tasks
,这导致了内存泄漏.我无法确定为什么我的任务没有完成(如果我在 运行 几天后在运行时查看任务列表,有数百个任务显示为“等待”)。大多数情况下,所有任务都完成并被处理掉;他们没有完成只是随机的。例如,在过去的 24 小时内,大约 12 小时内出现了 1 个问题,其中有 148 个等待任务从未完成。由于无法理解 Ping
挂起的原因(因为它是 .NET 的内部问题),我无法复制问题进行调试。
( 如果存在 DNS 问题,.NET 中的 Ping
调用可能会挂起并且内置超时会失败,这就是我在其中构建额外超时的原因)
如果使用 Task.Delay
和 CancellationToken
在 15 秒内 ping 没有 return,我有办法取消主循环。然后在每个 Ping 函数中我都有一个 Delay
以防 Ping 调用本身挂起并强制函数完成。另请注意,我只对 IPv4 执行 ping 操作;没有 IPv6 或 URL.
主循环
pingcancel = new CancellationTokenSource();
List<Task<MachinePingResults>> results = new List<Task<MachinePingResults>>();
try
{
foreach (var m in localMachines.FindAll(m => !m.Online))
results.Add(Task.Run(() =>
PingMachine(m.ipAddress, 8000), pingcancel.Token
));
await Task.WhenAny(Task.WhenAll(results.ToArray()), Task.Delay(15000));
pingcancel.Cancel();
}
catch (Exception ex) { Console.WriteLine(ex); }
finally
{
results.Where(r => r.IsCompleted).ToList()
.ForEach(r =>
//modify the online machines);
results.Where(r => r.IsCompleted).ToList().ForEach(r => r.Dispose());
results.Clear();
}
Ping 函数
static async Task<MachinePingResults> PingMachine(string ipaddress, int timeout)
{
try
{
using (Ping ping = new Ping())
{
var reply = ping.SendPingAsync(ipaddress, timeout);
await Task.WhenAny(Task.Delay(timeout), reply);
if (reply.IsCompleted && reply.Result.Status == IPStatus.Success)
{
return new MachinePingResults(ipaddress, true);
}
}
}
catch (Exception ex)
{
Debug.WriteLine("Error: " + ex.Message);
}
return new MachinePingResults(ipaddress, false);
}
如果 Ping 挂起,每个 Task
都有延迟让它继续,我不知道导致某些 Task<MachinePingResults>
永远无法完成的问题是什么。
如何确保使用 .NET Ping
的 Task
结束?
使用 .NET 5.0,问题发生在机器 运行 windows 10 和 windows server 2012
代码 posted 中有不少漏洞,但我尝试复制并最终重构了一点。
这个版本看起来非常健壮,对 SendAsync
的实际调用包含在适配器 class.
中
我接受这不一定直接回答问题,但在无法准确复制您的问题的情况下,提供另一种构建代码的方法可能消除问题。
async Task Main()
{
var masterCts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); // 15s overall timeout
var localMachines = new List<LocalMachine>
{
new LocalMachine("192.0.0.1", false), // Should be not known - TimedOut
new LocalMachine("192.168.86.88", false), // Should be not known - DestinationHostUnreachable (when timeout is 8000)
new LocalMachine("www.dfdfsdfdfdsgrdf.cdcc", false), // Should be not known - status Unknown because of PingException
new LocalMachine("192.168.86.87", false) // Known - my local IP
};
var results = new List<PingerResult>();
try
{
// Create the "hot" tasks
var tasks = localMachines.Where(m => !m.Online)
.Select(m => new Pinger().SendPingAsync(m.HostOrAddress, 8000, masterCts.Token))
.ToArray();
await Task.WhenAll(tasks);
results.AddRange(tasks.Select(t => t.Result));
}
finally
{
results.ForEach(r => localMachines.Single(m => m.HostOrAddress.Equals(r.HostOrAddress)).Online = r.Status == IPStatus.Success);
results.Dump(); // For LINQPad
localMachines.Dump(); // For LINQPad
results.Clear();
}
}
public class LocalMachine
{
public LocalMachine(string hostOrAddress, bool online)
{
HostOrAddress = hostOrAddress;
Online = online;
}
public string HostOrAddress { get; }
public bool Online { get; set; }
}
public class PingerResult
{
public string HostOrAddress {get;set;}
public IPStatus Status {get;set;}
}
public class Pinger
{
public async Task<PingerResult> SendPingAsync(string hostOrAddress, int timeout, CancellationToken token)
{
// Check if timeout has occurred
token.ThrowIfCancellationRequested();
IPStatus status = default;
try
{
var reply = await SendPingInternal(hostOrAddress, timeout, token);
status = reply.Status;
}
catch (PingException)
{
status = IPStatus.Unknown;
}
return new PingerResult
{
HostOrAddress = hostOrAddress,
Status = status
};
}
// Wrap the legacy EAP pattern offered by Ping.
private Task<PingReply> SendPingInternal(string hostOrAddress, int timeout, CancellationToken cancelToken)
{
var tcs = new TaskCompletionSource<PingReply>();
if (cancelToken.IsCancellationRequested)
{
tcs.TrySetCanceled();
}
else
{
using (var ping = new Ping())
{
ping.PingCompleted += (object sender, PingCompletedEventArgs e) =>
{
if (!cancelToken.IsCancellationRequested)
{
if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else
{
tcs.TrySetResult(e.Reply);
}
}
};
cancelToken.Register(() => { tcs.TrySetCanceled(); });
ping.SendAsync(hostOrAddress, timeout, new object());
}
};
return tcs.Task;
}
}
编辑:
我刚刚在评论中注意到您提到 ping“所有 1391”。在这一点上,我希望限制使用 SemaphoreSlim
同时发送的 ping 的数量。请参阅此博客 post(很久以前!)概述了该方法:https://devblogs.microsoft.com/pfxteam/implementing-a-simple-foreachasync/
我正在开发一个“心跳”应用程序,它每分钟通过循环 ping 数百个 IP 地址。 IP 地址存储在 class Machines
的列表中。我有一个循环,它为每个 IP 创建一个 Task<MachinePingResults>
(其中 MachinePingResults
基本上是一个 IP 和在线状态的元组)并使用 System.Net.NetworkInformation
.
我遇到的问题是,在 运行 数小时(或数天)之后,主程序的一个循环未能完成 Tasks
,这导致了内存泄漏.我无法确定为什么我的任务没有完成(如果我在 运行 几天后在运行时查看任务列表,有数百个任务显示为“等待”)。大多数情况下,所有任务都完成并被处理掉;他们没有完成只是随机的。例如,在过去的 24 小时内,大约 12 小时内出现了 1 个问题,其中有 148 个等待任务从未完成。由于无法理解 Ping
挂起的原因(因为它是 .NET 的内部问题),我无法复制问题进行调试。
(Ping
调用可能会挂起并且内置超时会失败,这就是我在其中构建额外超时的原因)
如果使用 Task.Delay
和 CancellationToken
在 15 秒内 ping 没有 return,我有办法取消主循环。然后在每个 Ping 函数中我都有一个 Delay
以防 Ping 调用本身挂起并强制函数完成。另请注意,我只对 IPv4 执行 ping 操作;没有 IPv6 或 URL.
主循环
pingcancel = new CancellationTokenSource();
List<Task<MachinePingResults>> results = new List<Task<MachinePingResults>>();
try
{
foreach (var m in localMachines.FindAll(m => !m.Online))
results.Add(Task.Run(() =>
PingMachine(m.ipAddress, 8000), pingcancel.Token
));
await Task.WhenAny(Task.WhenAll(results.ToArray()), Task.Delay(15000));
pingcancel.Cancel();
}
catch (Exception ex) { Console.WriteLine(ex); }
finally
{
results.Where(r => r.IsCompleted).ToList()
.ForEach(r =>
//modify the online machines);
results.Where(r => r.IsCompleted).ToList().ForEach(r => r.Dispose());
results.Clear();
}
Ping 函数
static async Task<MachinePingResults> PingMachine(string ipaddress, int timeout)
{
try
{
using (Ping ping = new Ping())
{
var reply = ping.SendPingAsync(ipaddress, timeout);
await Task.WhenAny(Task.Delay(timeout), reply);
if (reply.IsCompleted && reply.Result.Status == IPStatus.Success)
{
return new MachinePingResults(ipaddress, true);
}
}
}
catch (Exception ex)
{
Debug.WriteLine("Error: " + ex.Message);
}
return new MachinePingResults(ipaddress, false);
}
如果 Ping 挂起,每个 Task
都有延迟让它继续,我不知道导致某些 Task<MachinePingResults>
永远无法完成的问题是什么。
如何确保使用 .NET Ping
的 Task
结束?
使用 .NET 5.0,问题发生在机器 运行 windows 10 和 windows server 2012
代码 posted 中有不少漏洞,但我尝试复制并最终重构了一点。
这个版本看起来非常健壮,对 SendAsync
的实际调用包含在适配器 class.
我接受这不一定直接回答问题,但在无法准确复制您的问题的情况下,提供另一种构建代码的方法可能消除问题。
async Task Main()
{
var masterCts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); // 15s overall timeout
var localMachines = new List<LocalMachine>
{
new LocalMachine("192.0.0.1", false), // Should be not known - TimedOut
new LocalMachine("192.168.86.88", false), // Should be not known - DestinationHostUnreachable (when timeout is 8000)
new LocalMachine("www.dfdfsdfdfdsgrdf.cdcc", false), // Should be not known - status Unknown because of PingException
new LocalMachine("192.168.86.87", false) // Known - my local IP
};
var results = new List<PingerResult>();
try
{
// Create the "hot" tasks
var tasks = localMachines.Where(m => !m.Online)
.Select(m => new Pinger().SendPingAsync(m.HostOrAddress, 8000, masterCts.Token))
.ToArray();
await Task.WhenAll(tasks);
results.AddRange(tasks.Select(t => t.Result));
}
finally
{
results.ForEach(r => localMachines.Single(m => m.HostOrAddress.Equals(r.HostOrAddress)).Online = r.Status == IPStatus.Success);
results.Dump(); // For LINQPad
localMachines.Dump(); // For LINQPad
results.Clear();
}
}
public class LocalMachine
{
public LocalMachine(string hostOrAddress, bool online)
{
HostOrAddress = hostOrAddress;
Online = online;
}
public string HostOrAddress { get; }
public bool Online { get; set; }
}
public class PingerResult
{
public string HostOrAddress {get;set;}
public IPStatus Status {get;set;}
}
public class Pinger
{
public async Task<PingerResult> SendPingAsync(string hostOrAddress, int timeout, CancellationToken token)
{
// Check if timeout has occurred
token.ThrowIfCancellationRequested();
IPStatus status = default;
try
{
var reply = await SendPingInternal(hostOrAddress, timeout, token);
status = reply.Status;
}
catch (PingException)
{
status = IPStatus.Unknown;
}
return new PingerResult
{
HostOrAddress = hostOrAddress,
Status = status
};
}
// Wrap the legacy EAP pattern offered by Ping.
private Task<PingReply> SendPingInternal(string hostOrAddress, int timeout, CancellationToken cancelToken)
{
var tcs = new TaskCompletionSource<PingReply>();
if (cancelToken.IsCancellationRequested)
{
tcs.TrySetCanceled();
}
else
{
using (var ping = new Ping())
{
ping.PingCompleted += (object sender, PingCompletedEventArgs e) =>
{
if (!cancelToken.IsCancellationRequested)
{
if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else
{
tcs.TrySetResult(e.Reply);
}
}
};
cancelToken.Register(() => { tcs.TrySetCanceled(); });
ping.SendAsync(hostOrAddress, timeout, new object());
}
};
return tcs.Task;
}
}
编辑:
我刚刚在评论中注意到您提到 ping“所有 1391”。在这一点上,我希望限制使用 SemaphoreSlim
同时发送的 ping 的数量。请参阅此博客 post(很久以前!)概述了该方法:https://devblogs.microsoft.com/pfxteam/implementing-a-simple-foreachasync/