I am working on a "heartbeat" application that pings hundreds of IP addresses every minute via a loop. The IP addresses are stored in a list of a class Machines
. I have a loop that creates a Task<MachinePingResults>
(where MachinePingResults
is basically a Tuple of an IP and online status) for each IP and calls a ping function using System.Net.NetworkInformation
.
The issue I'm having is that after hours (or days) of running, one of the loops of the main program fails to finish the Tasks
which is leading to a memory leak. I cannot determine why my Tasks are not finishing (if I look in the Task list during runtime after a few days of running, there are hundreds of tasks that appear as "awaiting"). Most of the time all the tasks finish and are disposed; it is just randomly that they don't finish. For example, the past 24 hours had one issue at about 12 hours in with 148 awaiting tasks that never finished. Due to the nature of not being able to see why the Ping
is hanging (since it's internal to .NET), I haven't been able to replicate the issue to debug.
(It appears that the Ping
call in .NET can hang and the built-in timeout fail if there is a DNS issue, which is why I built an additional timeout in)
I have a way to cancel the main loop if the pings don't return within 15 seconds using Task.Delay
and a CancellationToken
. Then in each Ping function I have a Delay
in case the Ping call itself hangs that forces the function to complete. Also note I am only pinging IPv4; there is no IPv6 or URL.
Main Loop
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();
}
The Ping Function
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);
}
With every Task
having a Delay to let it continue if the Ping hangs, I don't know what would be the issue that is causing some of the Task<MachinePingResults>
to never finish.
How can I ensure a Task
using .NET Ping
ends?
Using .NET 5.0 and the issues occurs on machines running windows 10 and windows server 2012
Ping
but it might hang in the synchronous part, try putting the call toSendPingAsync
in a Task.Run. (var reply = Task.Run(async () => await ping.SendPingAsync(ipaddress, timeout));
). This won't fix the memory leak but will at least allowPingMachine
to always return. – GuanineTasks
that are never executed or disposed of – NatiePing
throws an exception, it hangs the function and task. – NatieSendPingAsync
operations, by using theParallel.ForEachAsync
or something. I don't expect this to solve the problem though. – SedentaryTask.WhenAny
is already an attempt to fix it? Given that ping has its own timeout, much of the PingMachine method seems redundant.. – KrellPing.SendAsync
use the synchronousDns.GetAddresses
even though it should use the async. So it might be worth splitting those two up yourself and callingDns.GetHostAddressesAsync
manually, and passingPing
just the IP addresses. You can see this bug in referencesource for Framework 4 and the fix in Runtime 5.0+ – Uriasif (!IPAddress.TryParse(hostNameOrAddress...
there. – Sedentary