Search code examples
c#timeoutping

Why Ping timeout is not working correctly?


I have 5 pc and i want to ping this pc's are available or no. So I'm using c# Ping class. Two pc are available but the other 3 pc are closed when i ping them my program wait min 7 seconds for response.

I just want to check 1000 miliseconds and returns OK or ERROR...

How can i control ping timeout?

Here my code

        foreach (var item in listofpc)
        {
            Stopwatch timer = Stopwatch.StartNew();
            try
            {
                Ping myPing = new Ping();
                PingReply reply = myPing.Send(ServerName, 500);
                if (reply != null)
                {
                    timer.Stop();
                    TimeSpan timeTaken = timer.Elapsed;
                    Log.append("PING OK TimeTaken="+ timeTaken.ToString() + " Miliseconds", 50);
                }

            }
            catch (Exception ex)
            {
                timer.Stop();
                TimeSpan timeTaken = timer.Elapsed;
                Log.append("PING ERROR  TimeTaken=" +
                   timeTaken.ToString() + " Miliseconds \n" + ex.ToString(), 50);

            }
        }

But when i check my logs I saw response times are 2 seconds. Why ping timeout value is not working?

Any idea?


Solution

  • UPDATE: The Why: The most probable reason for the Ping timeout not working, as stated by others as well, is DNS resolution. The system call to getaddrinfo (the one used by Dns.GetHostAddresses and Ping - https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo, https://learn.microsoft.com/en-us/dotnet/api/system.net.dns.gethostaddresses?view=netcore-3.1 - similar for Full Framework) does not accept timeouts. As such, a further improvement to the code below would be to separate the dns lookup from the pinging. Do the lookup first with a timeout approach similar to the code below, and only ping IPs rather than host names, with a specified timeout.

    I've run into similar issues in the past, and I have some code that might help in working around this issue. I am editing it here, so it might be less than 100% correct as is, and a bit more complicated than your needs. Can you try something like this?

    The hammer: (full code with test results also included below)

    private static PingReply ForcePingTimeoutWithThreads(string hostname, int timeout)
    {
        PingReply reply = null;
        var a = new Thread(() => reply =  normalPing(hostname, timeout));
        a.Start();
        a.Join(timeout); //or a.Abort() after a timeout, but you have to take care of a ThreadAbortException in that case... brrr I like to think that the ping might go on and be successful in life with .Join :)
        return reply;
    }
    
    private static PingReply normalPing(string hostname, int timeout)
    {
       try
       {
          return new Ping().Send(hostname, timeout);
       }
       catch //never do this kids, this is just a demo of a concept! Always log exceptions!
       {
          return null; //or this, in such a low level method 99 cases out of 100, just let the exception bubble up
        }
     }
    

    Here is a full working sample (Tasks.WhenAny tested and working in version 4.5.2). I also learned that the elegance of Tasks comes at a more significant performance hit than I remember, but Thread.Join/Abort are too brutal for most production environments.

    using System;
    using System.Diagnostics;
    using System.Net.NetworkInformation;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ConsoleApp1
    {
        class Program
        {
            //this can easily be async Task<PingReply> or even made generic (original version was), but I wanted to be able to test all versions with the same code
            private static PingReply PingOrTimeout(string hostname, int timeOut)
            {
                PingReply result = null;
                var cancellationTokenSource = new CancellationTokenSource();
                var timeoutTask = Task.Delay(timeOut, cancellationTokenSource.Token);
    
                var actionTask = Task.Factory.StartNew(() =>
                {
                    result = normalPing(hostname, timeOut);
                }, cancellationTokenSource.Token);
    
                Task.WhenAny(actionTask, timeoutTask).ContinueWith(t =>
                {
                    cancellationTokenSource.Cancel();
                }).Wait(); //if async, remove the .Wait() and await instead!
                
                return result;
            }
    
            private static PingReply normalPing(string hostname, int timeout)
            {
                try
                {
                    return new Ping().Send(hostname, timeout);
                }
                catch //never do this kids, this is just a demo of a concept! Always log exceptions!
                {
                    return null; //or this, in such a low level method 99 cases out of 100, just let the exception bubble up
                }
            }
    
            private static PingReply ForcePingTimeoutWithThreads(string hostname, int timeout)
            {
                PingReply reply = null;
                var a = new Thread(() => reply =  normalPing(hostname, timeout));
                a.Start();
                a.Join(timeout); //or a.Abort() after a timeout... brrr I like to think that the ping might go on and be successful in life with .Join :)
                return reply;
            }
    
            static byte[] b = new byte[32];
            static PingOptions po = new PingOptions(64, true);
            static PingReply JimiPing(string hostname, int timeout)
            {
                try
                {
                    return new Ping().Send(hostname, timeout, b, po);
                }
                catch //never do this kids, this is just a demo of a concept! Always log exceptions!
                {
                    return null; //or this, in such a low level method 99 cases out of 100, just let the exception bubble up
                }
            }
    
            static void RunTests(Func<string, int, PingReply> timeOutPinger)
            {
                var stopWatch = Stopwatch.StartNew();
                var expectedFail = timeOutPinger("bogusdjfkhkjh", 200);
                Console.WriteLine($"{stopWatch.Elapsed.TotalMilliseconds} false={expectedFail != null}");
                stopWatch = Stopwatch.StartNew();
                var expectedSuccess = timeOutPinger("127.0.0.1", 200);
                Console.WriteLine($"{stopWatch.Elapsed.TotalMilliseconds} true={expectedSuccess != null && expectedSuccess.Status == IPStatus.Success}");
            }
    
            static void Main(string[] args)
            {
                RunTests(normalPing);
                RunTests(PingOrTimeout);
                RunTests(ForcePingTimeoutWithThreads);
                RunTests(JimiPing);
                
                Console.ReadKey(false);
            }
        }
    }
    

    Some results from my testing:

    >Running ping timeout tests timeout = 200. method=normal
    >
    > - host: bogusdjfkhkjh elapsed: 2366,9714 expected: false=False
    > - host: 127.0.0.1 elapsed: 4,7249 expected: true=True
    >
    >Running ping timeout tests timeout = 200. method:ttl+donotfragment (Jimi)
    >
    > - host: bogusdjfkhkjh elapsed: 2310,836 expected: false actual: False
    > - host: 127.0.0.1 elapsed: 0,7838 expected: true actual: True
    >
    >Running ping timeout tests timeout = 200. method:tasks
    >
    > - host: bogusdjfkhkjh elapsed: 234,1491 expected: false actual: False
    > - host: 127.0.0.1 elapsed: 3,2829 expected: true=True
    >
    >Running ping timeout tests timeout = 200. method:threads
    >
    > - host: bogusdjfkhkjh elapsed: 200,5357 expected: false actual:False
    > - host: 127.0.0.1 elapsed: 5,5956 expected: true actual: True
    

    Caution For the Tasks version, even if the calling thread is "unblocked", the action itself, in this case the ping, might linger until it actually times out. That is why I suggest putting in a timeout for the ping command itself as well.

    UPDATE Researching the why too, but thought a workaround would help you for now.

    New findings: