C# multiple pinging in loop

4

I need to create application which will be pinging multiple addresses in loop. I read a lot of examples here at stackoverflow and finally got working code:

    public void Check(List<string> addresses)
    {
        List<Task<PingReply>> pingTasks = new List<Task<PingReply>>();
        foreach (string address in addresses)
        {
            pingTasks.Add(PingAsync(address));
        }

        Task.Factory.ContinueWhenAll(pingTasks.ToArray(), _ => { }).ContinueWith(t =>
        {
            StringBuilder pingResult = new StringBuilder();
            foreach (var pingTask in pingTasks)
            {
                pingResult.Append(pingTask.Result.Address);
                pingResult.Append("    ");
                pingResult.Append(pingTask.Result.Status);
                pingResult.Append("    ");
                pingResult.Append(pingTask.Result.RoundtripTime.ToString());
                pingResult.Append("   \n");
            }
            Console.WriteLine(pingResult.ToString());
        },
        CancellationToken.None,
        TaskContinuationOptions.None,
        TaskScheduler.FromCurrentSynchronizationContext());
    }

    public static Task<PingReply> PingAsync(string address)
    {
        var tcs = new TaskCompletionSource<PingReply>();
        using (Ping ping = new Ping())
        {
            ping.PingCompleted += (obj, sender) =>
            {
                tcs.SetResult(sender.Reply);
            };
            ping.SendAsync(address, new object());
        }
        return tcs.Task;
    }

Now I need to change this code to works with await and async, and then execute this in loop with intervals. And here my problem starts. I have no idea how to use async in this case, I read many articles and now I'm confused because my code still doesn't work. Can you explain me step by step how to change my code to work with await? Can you explain me how can I put it inside while loop with executing intervals? I tried to put entire 'Check' function into the loop and add Thread.Sleep(interval) at the end but I have this strange feeling that I'm doing something wrong/unefficient. I need to ping 400 servers in 1 second. Is it even possible? Regards

UPDATE 1: I have code so far:

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Linq;

namespace Pinging
{
    class CheckPing
    {
        public async Task LoopAndCheckPingAsync(List<string> addresses)
        {
            while (true)
            {
                var ping = new Ping();
                var pingTasks = addresses.Select(address => ping.SendPingAsync(address));

                await Task.WhenAll(pingTasks);

                StringBuilder pingResultBuilder = new StringBuilder();

                foreach (var pingReply in pingTasks)
                {
                    pingResultBuilder.Append(pingReply.Result.Address);
                    pingResultBuilder.Append("    ");
                    pingResultBuilder.Append(pingReply.Result.Status);
                    pingResultBuilder.Append("    ");
                    pingResultBuilder.Append(pingReply.Result.RoundtripTime.ToString());
                    pingResultBuilder.AppendLine();
                }

                Console.WriteLine(pingResultBuilder.ToString());

                await Task.Delay(TimeSpan.FromMinutes(5));
            }
        }
    }
}

and calling:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Pinging
{
    public class Class1
    {
        static void Main()
        {
            List<string> addresses = new List<string>() { "www.google.pl", "212.77.100.101" };

            CheckPing c = new CheckPing();
            Task.Factory.StartNew(() => c.LoopAndCheckPingAsync(addresses));

            Console.Read();
        }
    }
}

I tried call LoopAndCheckPingAsync from Main with different ways, but still freezes. This is my last try.

EDIT 2: I made some changes to see application performance and now my code looks like:

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Linq;

namespace Pinging
{
    class CheckPing
    {
        public async Task LoopAndCheckPingAsync(List<string> addresses)
        {
            while (true)
            {
                var pingTasks = addresses.Select(address =>
                {
                    return new Ping().SendPingAsync(address);
                });

                await Task.WhenAll(pingTasks);

                StringBuilder pingResultBuilder = new StringBuilder();

                foreach (var pingReply in pingTasks)
                {
                    pingResultBuilder.Append(pingReply.Result.Address);
                    pingResultBuilder.Append("    ");

                    pingResultBuilder.Append(pingReply.Result.Status);
                    pingResultBuilder.Append("    ");

                    pingResultBuilder.Append(pingReply.Result.RoundtripTime.ToString());
                    pingResultBuilder.AppendLine();
                }

                Console.WriteLine(pingResultBuilder.ToString());
                Functions.counter++;

                if (Functions.counter >= 100) break;

                await Task.Delay(TimeSpan.FromSeconds(1));
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Pinging
{
    public class Class1
    {
        static void Main()
        {
            List<string> addresses = Functions.Read(@"C:\Users\Adam\Desktop\addresses.csv");

            Functions.start = DateTime.Now;

            CheckPing c = new CheckPing();
            c.LoopAndCheckPingAsync(addresses).Wait();

            Console.WriteLine(Functions.counter);

            Console.Read();
        }
    }
}

I am using standard site addresses readed from file:

www.google.com
www.yahoo.com
www.live.com
www.msn.com
www.facebook.com
www.youtube.com
www.microsoft.com
www.wikipedia.org
www.myspace.com
www.ebay.com
www.aol.com
www.ask.com
www.craigslist.org
www.blogspot.com
www.answers.com
www.about.com
www.amazon.com
www.mapquest.com
www.windows.com
www.adobe.com
www.photobucket.com
www.wordpress.com
www.go.com
www.paypal.com
www.walmart.com
www.reference.com
www.cnn.com
www.twitter.com
www.imdb.com
www.flickr.com
www.att.com
www.cnet.com
www.irs.gov
www.whitepages.com
www.yellowpages.com
www.comcast.net
www.target.com
www.simplyhired.com
www.webmd.com
www.weather.com
www.blogger.com
www.bankofamerica.com
www.apple.com
www.chase.com
www.bizrate.com
www.hulu.com
www.merriam-webster.com
www.geocities.com
www.ehow.com
www.ezinearticles.com

EDIT 3: Now everything works perfectly but here is another issue I need to handle. When I test 100000 pings after 5 minutes I get out of memory exception. Is there way to handle this somehow? Maybe dividing to blocks and destroys old classes?

EDIT 4: Error content:

System.OutOfMemoryException was unhandled HResult=-2147024882 Message=Exception of type 'System.OutOfMemoryException' was thrown. Source=mscorlib StackTrace: at System.Exception.Init() at System.InvalidOperationException..ctor(String message, Exception innerException) at System.Net.NetworkInformation.PingException..ctor(String message, Exception innerException) at System.Net.NetworkInformation.Ping.ContinueAsyncSend(Object state) at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() at System.Threading.ThreadPoolWorkQueue.Dispatch() at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
InnerException:

EDIT 5 After adding using statement I get "not enough storage is available to process this command" error:

System.AggregateException was unhandled HResult=-2146233088
Message=One or more errors occurred. Source=mscorlib StackTrace: at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at System.Threading.Tasks.Task.Wait() at Pinging.Class1.Main() at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException: System.Net.NetworkInformation.PingException HResult=-2146233079 Message=An exception occurred during a Ping request. Source=mscorlib StackTrace: at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at Pinging.CheckPing.d__2.MoveNext() InnerException: System.ApplicationException HResult=-2147024888 Message=Not enough storage is available to process this command (Exception from HRESULT: 0x80070008) Source=mscorlib StackTrace: at System.Threading.ThreadPool.RegisterWaitForSingleObjectNative(WaitHandle waitHandle, Object state, UInt32 timeOutInterval, Boolean executeOnlyOnce, RegisteredWaitHandle registeredWaitHandle, StackCrawlMark& stackMark, Boolean compressStack) at System.Threading.ThreadPool.RegisterWaitForSingleObject(WaitHandle waitObject, WaitOrTimerCallback callBack, Object state, UInt32 millisecondsTimeOutInterval, Boolean executeOnlyOnce, StackCrawlMark& stackMark, Boolean compressStack) at System.Threading.ThreadPool.RegisterWaitForSingleObject(WaitHandle waitObject, WaitOrTimerCallback callBack, Object state, Int32 millisecondsTimeOutInterval, Boolean executeOnlyOnce) at System.Net.NetworkInformation.Ping.InternalSend(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options, Boolean async) at System.Net.NetworkInformation.Ping.ContinueAsyncSend(Object state) InnerException:

c#
.net
multithreading
async-await
ping
asked on Stack Overflow Aug 22, 2014 by Adam Mrozek • edited Feb 5, 2015 by Yuval Itzchakov

2 Answers

5

Lets walk through what we want to do:

  1. We want to begin the while loop when we first execute the method

  2. We want to generate a bunch of tasks which will be used to send the ping request. For that we can use Ping.SendPingAsync. We will project each element from the list of addresses using Enumerable.Select

  3. We will wait untill all tasks finish executing. For that we will await on Task.WhenAll.

  4. When all tasks finish executing the ping request, we will iterate them using a foreach loop.

  5. We will wait on the interval time between the calls. We wont use Thread.Sleep, as it is a blocking call. We will instead use Task.Delay will internaly uses a Timer. When we await on it, control will yield back to the method that called us.

This is how it comes out:

private static async Task LoopAndCheckPingAsync(List<string> addresses)
{  
    StringBuilder pingResultBuilder = new StringBuilder();        

    while (true)
    {
         var pingTasks = addresses.Select(address =>
         {
             using (var ping = new Ping())
             {
                 return ping.SendPingAsync(address);
             }
         }).ToList();    

        await Task.WhenAll(pingTasks);

        foreach (var pingReply in pingTasks)
        {                pingResultBuilder.Append(pingReply.Result.Address);
            pingResultBuilder.Append("    ");
            pingResultBuilder.Append(pingReply.Result.Status);
            pingResultBuilder.Append("    ");

            pingResultBuilder.Append(pingReply.Result.RoundtripTime.ToString());
            pingResultBuilder.AppendLine();
        }

        Console.WriteLine(pingResultBuilder.ToString());
        pingResultBuilder.Clear();

        await Task.Delay(TimeSpan.FromMinutes(5));
    }
}

Note the method now returns a Task instead of void, because we need to await on our method (note async tends to spread in your codebase once you start using it).

Edit

After looking a bit deeper into the Ping class, apparently we can't execute multiple ping requests on the same Ping instance (Look into Ping.CheckStart which checks if there's an ongoing request, and if there is throws an InvalidOperationException), which is exactly what we do in our Select method. To work around that problem, we can create an instance of the Ping class for each request. Note this will add some memory pressure to your application. If you have 1000 requests going on concurrently, that means you will have 1000 instances of the Ping class in memory while making those requests.

Another thing to note is you're running inside a Console Application which uses the ThreadPoolSynchronizationContext internally. There is no need to make a call to Task.Run to execute our method, you can use Task.Wait to keep the console application alive while making the requests. It is better to use Wait, so we can see if any exception propagated from our method.

static void Main()
{
    List<string> addresses = new List<string>() { "www.google.pl", "212.77.100.101" };

    CheckPing c = new CheckPing();
    c.LoopAndCheckPingAsync(addresses).Wait();
}
answered on Stack Overflow Aug 22, 2014 by Yuval Itzchakov • edited Aug 23, 2014 by Yuval Itzchakov
0

I would suggest you look at using Microsoft's Reactive Framework (NuGet "Rx-Main") for this. This is what the code would look like:

private static void LoopAndCheckPingAsync(List<string> addresses)
{
    Func<string, IObservable<PingReply>> getPingReply = a =>
        Observable.Using(
            () => new Ping(),
            p => Observable.FromAsync<PingReply>(() => p.SendPingAsync(a)));

    var query =
        from n in Observable.Interval(TimeSpan.FromSeconds(5)).StartWith(-1L)
        from ps in
        (
            from a in addresses.ToObservable()
            from pr in getPingReply(a)
            select pr
        ).ToArray()
        select String.Join(
            Environment.NewLine,
            ps.Select(p => String.Format("{0}    {1}    {2}",
                    p.Address,
                    p.Status,
                    p.RoundtripTime)));

    query.Subscribe(x => Console.WriteLine(x));
}

It's all async and follows your requirements as far as I can see.

answered on Stack Overflow Feb 5, 2015 by Enigmativity

User contributions licensed under CC BY-SA 3.0