1

With the help of Google and community, I was able to build a nice set of methods allowing me to asynchronously call a function. This function is testing remote host properties, so it is idling most of the time. For this reason I would like to maximize the number of concurrent threads launched such that all calls can be processed in the minimum amount of time.

Here is the Code I have so far:

// Check remote host connectivity
public static class CheckRemoteHost
{

    // Private Class members
    private static bool AllDone     = false;
    private static object lockObj   = new object();
    private static List<string> IPs;


    // Wrapper: manage async method <Ping>
    public static List<string> Ping(HashSet<string> IP_Ports, int TimeoutInMS = 100)
    {// async worker method:  check remote host via <Ping>

        // Locals
        IPs = new List<string>();

        // Perform remote host check
        AllDone = false;
        Ping_check(IP_Ports, TimeoutInMS);
        while (!AllDone) { CommonLib.Utils.ApplicationWait(10, 10); }

        // Finish
        return IPs;
    }
    private static async void Ping_check(HashSet<string> IP_Ports, int timeout)
    {
        // Locals
        var tasks = new List<Task>();

        // Build task-set for parallel Ping checks
        foreach (string host in IP_Ports)
        {
            var task = PingAndUpdateAsync(host, timeout);
            tasks.Add(task);
        }

        // Start execution queue
        await Task.WhenAll(tasks).ContinueWith(t =>
        {
            AllDone = true;
        });

    }
    private static async Task PingAndUpdateAsync(string ip, int timeout)
    {

        // Locals
        System.Net.NetworkInformation.Ping ping;
        System.Net.NetworkInformation.PingReply reply;

        try
        {
            ping    = new System.Net.NetworkInformation.Ping();
            reply   = await ping.SendPingAsync(ip, timeout);

            if(reply.Status == System.Net.NetworkInformation.IPStatus.Success)
            {
                lock (lockObj)
                {
                    IPs.Add(ip);
                }
            }
        }
        catch
        {
            // do nothing
        }
    }

}// end     public static class CheckRemoteHost

This code is tested quite extensively, and the code seems stable and reliably report live hosts. Having said that, I know that it only spawns 8 threads at a time (= number of logical core on my test machine).

The key portion of the code is this:

// Start execution queue
await Task.WhenAll(tasks).ContinueWith(t =>
{
    AllDone = true;
});

This is where I would like to increase/ maximize the number of concurrently launched threads to something like 25 per core (remember the thread job is 99% idle).

So far, my thread concurrency research has brought up the explicit thread and Parallel.For approaches. However, these seem to have the same shortcoming of spawning no more than 8 threads.

Any help would be very much appreciated, so thank you very much in advance everyone for looking!

supersausage007
  • 93
  • 1
  • 10
  • 2
    Something like [this](https://stackoverflow.com/questions/13911473/multithreading-c-sharp-gui-ping-example) ? – Eser Jun 28 '18 at 20:30
  • 4
    task != thread. you can have thousands of tasks running on a single thread. with an async workflow, you don't want more threads than you have cores -- you want the fewest number of threads as possible. – Cory Nelson Jun 28 '18 at 20:39
  • @CoryNelson `You don't want more threads than you have cores` *A cliche*. If all my threads are blocked with some IO (disk, NW), why would I not create a new one when all cores are idle. – Eser Jun 28 '18 at 20:43
  • 4
    @Eser as I said, *for an async workflow*. You should not have any blocking I/O in async. – Cory Nelson Jun 28 '18 at 20:44
  • @Eser Thank you that is a very good start! I did not find this during my own research. However, I still don't understand why your referenced method would be able to check "thousands of computers" in a few seconds (especially at 1500ms timeout). Clearly more than 8 threads are started, so could you please elaborate where my code falls short in terms of maximizing thread concurrency? – supersausage007 Jun 28 '18 at 20:46
  • 1
    @supersausage007 You don't have to wait the answer of the previous ping before sending a new one. That's why in the linked example there's one thread dedicated to sending them, and one thread dedicated to reading the replies. – Kevin Gosse Jun 28 '18 at 20:51
  • Also, `I know that it only spawns 8 threads at a time` what do you base that assumption on? You're using the threadpool. You cannot predict how many threads the threadpool is going to use. – Kevin Gosse Jun 28 '18 at 20:55
  • @KevinGosse Presumably they looked at the number of threads actually being used when running the program. In other words, the statement you quoted is them saying that, when they run this program on their machine, the thread pool appears to be choosing to start 8 threads. It's not saying that their code restricts the code to only using 8 threads. – Servy Jun 28 '18 at 20:58
  • OP, you really need to read this: [there is no thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html). The whole point of async is that you don't need to create a bunch of threads to wait on I/O. You only need threads if you are CPU-bound (mostly). You could probably do all of this on a single thread with multiple tasks. – John Wu Jun 28 '18 at 20:59

1 Answers1

1

You're making your life hard with the code you have. It's got a lot of plumbing that isn't needed and you're sharing static fields that would cause your code to fail if you called Ping a second time while the first one is running.

You need to get rid of all of that stuff.

I'd suggest using Microsoft's Reactive Framework - just NuGet "System.Reactive" and add using System.Reactive.Linq; to your code. Then you can do this:

public static class CheckRemoteHost
{
    public static IList<string> Ping(HashSet<string> IP_Ports, int TimeoutInMS = 100)
    {
        var query =
            from host in IP_Ports.ToObservable()
            from status in Observable.FromAsync(() => PingAsync(host, TimeoutInMS))
            where status
            select host;

        return query.ToList().Wait();
    }

    private static async Task<bool> PingAsync(string ip, int timeout)
    {
        try
        {
            var ping = new System.Net.NetworkInformation.Ping();
            var reply = await ping.SendPingAsync(ip, timeout);

            return reply.Status == System.Net.NetworkInformation.IPStatus.Success;
        }
        catch
        {
            return false;
        }
    }
}

That's it. That's all of the code you need. It's automatically maximising the thread use to get the job done.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • 1
    thank you for that, your code works great, and is certainly more elegant than my solution. For some a caveat might be that it requires .NET 4.6 or higher for the System.Reactive.Linq to be installable via GNuGet. In terms of performance both my original and your more elegant solutions are the same (2300 ms for +4500 IPs with 1500ms ping timeout - very efficient :) ) I marked your contribution as the answer, because your code is more elegant and concise. – supersausage007 Jul 05 '18 at 10:06