2

I am trying to construct a simple class, which calls a reboot function depending on the machine type to be rebooted. The called methods refer to a library which contains public static methods. I want to asynchronously call these static methods using Task in order to call the reboot methods in parallel. Here is the code so far:

EDIT Following the community's request, this is now a version of the same question, with the code below compiling. Please not that you need the Renci.SshNet lib, and also need to set references to it in your project.

// libs
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;

using Renci.SshNet;



namespace ConsoleApp
{
    class Program
    {


        // Simple Host class
        public class CHost
        {
            public string IP;
            public string HostType;

            public CHost(string inType, string inIP)
            {// constructor
                this.IP         = inIP;
                this.HostType   = inType;
            }
        }



        // Call test function
        static void Main(string[] args)
        {

            // Create a set of hosts
            var HostList = new List<CHost>();
            HostList.Add( new CHost("Machine1", "10.52.0.93"));
            HostList.Add( new CHost("Machine1", "10.52.0.30"));
            HostList.Add( new CHost("Machine2", "10.52.0.34"));


            // Call async host reboot call
            RebootMachines(HostList);
        }




        // Reboot method
        public static async void RebootMachines(List<CHost> iHosts)
        {
            // Locals
            var tasks = new List<Task>();


            // Build list of Reboot calls - as a List of Tasks
            foreach(var host in iHosts)
            {

                if (host.HostType == "Machine1")
                {// machine type 1
                    var task = CallRestartMachine1(host.IP);
                    tasks.Add(task);    // Add task to task list
                }
                else if (host.HostType == "Machine2")
                {// machine type 2
                    var task = CallRestartMachine2(host.IP);
                    tasks.Add(task);    // Add task to task list
                }   
            }


            // Run all tasks in task list in parallel
            await Task.WhenAll(tasks);
        }



        // ASYNC METHODS until here
        private static async Task CallRestartMachine1(string host)
        {// helper method: reboot machines of type 1

            // The compiler complains here (RebootByWritingAFile is a static method)
            // Error: "This methods lacks await operators and will run synchronously..."
            RebootByWritingAFile(@"D:\RebootMe.bm","reboot");

        }
        private static async Task CallRestartMachine2(string host)
        {// helper method: reboot machines of type 2

            // The compiler warns here (RebootByWritingAFile is a static method)
            // Error: "This methods lacks await operators and will run synchronously..."
            RebootByNetwork(host,"user","pwd");

        }




        // STATIC METHODS here, going forward
        private static void RebootByWritingAFile(string inPath, string inText)
        {// This method does a lot of checks using more static methods, but then only writes a file


            try
            {
                File.WriteAllText(inPath, inText); // static m
            }
            catch
            {
                // do nothing for now
            }
        }
        private static void RebootByNetwork(string host, string user, string pass)
        {
            // Locals
            string rawASIC = "";
            SshClient SSHclient;
            SshCommand SSHcmd;


            // Send reboot command to linux machine
            try
            {
                SSHclient = new SshClient(host, 22, user, pass);
                SSHclient.Connect();
                SSHcmd = SSHclient.RunCommand("exec /sbin/reboot");
                rawASIC = SSHcmd.Result.ToString();
                SSHclient.Disconnect();
                SSHclient.Dispose();
            }
            catch
            {
                // do nothing for now
            }
        }




    }
}

My only problem with this setup so far is that the static methods are called immediately (sequentially) and not assigned to a task. For example the line

        ...
        else if (host.HostType == "Machine2")
        {// machine type 2
            var task = CallRestartMachine2(host.IP);
            tasks.Add(task);    // Add task to task list
        }  
        ...

takes 20 seconds to execute if the host is unreachable. If 10 hosts are unreachable the sequential duration is 20*10 = 200 seconds.

I am aware of some seemingly similar questions such as

However, the cited lambda expressions still leave me with the same compiler error ["This methods lacks await operators..."]. Also, I do not want to spawn explicit threads (new Thread(() => ...)) due to high overhead if restarting a large number of machine in a cluster.

I may need to reboot a large number of machines in a cluster. Hence my question: How can I change my construct in order to be able to call the above static methods in parallel?

EDIT Thanks to the comments of @JohanP and @MickyD, I would like to elaborate that I have actually tried writing the async version of both static methods. However that sends me down a rabbit hole, where every time a static method is called within the async method I get the compiler warning that the call will be synchronous. Here is an example of how I tried to wrap the call to method as an async task, hoping to call the dependent methods in an async manner.

private static async Task CallRestartMachine1(string host)
{// helper method: reboot machines of type 1

    // in this version, compiler underlines '=>' and states that 
    // method is still called synchronously
    var test = await Task.Run(async () =>
    {
        RebootByWritingAFile(host);
    });

}

Is there a way to wrap the static method call such that all static child methods don't all need to rewritten as async?

Thank you all in advance.

3
  • How can you run several methods in parallel? By running them in different threads. Don't like threads? Re-write the methods to be async and await them. Commented Oct 25, 2018 at 22:21
  • 1
    This code wouldn't even compile so it's hard for us to help you since this is clearly not what your code looks like Commented Oct 25, 2018 at 22:30
  • @Dave, the code now compiles once the Renci.SHH lib is installed and referenced. Commented Oct 26, 2018 at 14:15

3 Answers 3

0

Your code has a strange mix of async with continuations and it won't even compile. You need to make it async all the way up. When you call RebootMachines(...) and that call can't be awaited, you can schedule continuations on that i.e. RebootMachines(...).ContinueWith(t=> Console.WriteLine('All Done'))

public static async Task RebootMachines(List<CHost> iHosts)
{
    var tasks = new List<Task>();

    // Build list of Reboot calls - as a List of Tasks
    foreach(var host in iHosts)
    {
        if (host.HostType == "Machine1")
        {// machine type 1
             task = CallRestartMachine1(host.IP);
        }
        else if (host.HostType == "Machine2")
        {// machine type 2
            task = CallRestartMachine2(host.IP);
        }

         // Add task to task list - for subsequent parallel processing
         tasks.Add(task);
    }


    // Run all tasks in task list in parallel
    await Task.WhenAll(tasks);
}

private static async Task CallRestartMachine1(string host)
{// helper method: reboot machines of type 1

    //RebootByWritingAFile is method that returns a Task, you need to await it
    // that is why the compiler is warning you
    await RebootByWritingAFile(host);

}

private static async Task CallRestartMachine2(string host)
{// helper method: reboot machines of type 2

    await RebootByNetwork(host);

}
Sign up to request clarification or add additional context in comments.

3 Comments

Good answer. The only thing I would add is that now the methods are async (or you have made additional async methods) it is generally a best practice to rename them with an Async suffix. learn.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Thank you everyone for your input. I edited my question to show what I have tried additionally to the simple code construct. Essentially, If I called the methods on a new thread for each invocation, I would end up with a quasi-async execution but with explicit thread switching overhead.
@JohanP, the code now compiles once the Renci.SHH lib is installed and referenced.
0

Everyone, thanks for your input and your help. I have played around with his over the last couple of days, and have come up with the following way to asynchronously call a static method:

// libs
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;

using Renci.SshNet;



namespace ConsoleApp
{
    class Program
    {


        // Simple Host class
        public class CHost
        {
            public string IP;
            public string HostType;

            public CHost(string inType, string inIP)
            {// constructor
                this.IP         = inIP;
                this.HostType   = inType;
            }
        }



        // Call test function
        static void Main(string[] args)
        {

            // Create a set of hosts
            var HostList = new List<CHost>();
            HostList.Add( new CHost("Machine1", "10.52.0.93"));
            HostList.Add( new CHost("Machine1", "10.52.0.30"));
            HostList.Add( new CHost("Machine2", "10.52.0.34"));


            // Call async host reboot call
            RebootMachines(HostList);
        }




        // Reboot method
        public static async void RebootMachines(List<CHost> iHosts)
        {
            // Locals
            var tasks = new List<Task>();


            // Build list of Reboot calls - as a List of Tasks
            foreach(var host in iHosts)
            {

                if (host.HostType == "Machine1")
                {// machine type 1
                     var task = CallRestartMachine1(host.IP);
                    tasks.Add(task);    // Add task to task list
                }
                else if (host.HostType == "Machine2")
                {// machine type 2
                    var task = CallRestartMachine2(host.IP);
                    tasks.Add(task);    // Add task to task list
                }   
            }


            // Run all tasks in task list in parallel
            await Task.WhenAll(tasks);
        }



        // ASYNC METHODS until here
        private static async Task CallRestartMachine1(string host)
        {// helper method: reboot machines of type 1

            await Task.Run(() =>
            {
                RebootByWritingAFile(@"D:\RebootMe.bm", "reboot");
            });

        }
        private static async Task CallRestartMachine2(string host)
        {// helper method: reboot machines of type 2

            await Task.Run(() =>
            {
                RebootByNetwork(host, "user", "pwd");
            });

        }




        // STATIC METHODS here, going forward
        private static void RebootByWritingAFile(string inPath, string inText)
        {// This method does a lot of checks using more static methods, but then only writes a file


            try
            {
                File.WriteAllText(inPath, inText); // static m
            }
            catch
            {
                // do nothing for now
            }
        }
        private static void RebootByNetwork(string host, string user, string pass)
        {
            // Locals
            string rawASIC = "";
            SshClient SSHclient;
            SshCommand SSHcmd;


            // Send reboot command to linux machine
            try
            {
                SSHclient = new SshClient(host, 22, user, pass);
                SSHclient.Connect();
                SSHcmd = SSHclient.RunCommand("exec /sbin/reboot");
                rawASIC = SSHcmd.Result.ToString();
                SSHclient.Disconnect();
                SSHclient.Dispose();
            }
            catch
            {
                // do nothing for now
            }
        }




    }
}

This setup calls my static methods asynchronously. I hope this helps someone who was stuck with a similar problem. Thanks again for all your input.

Comments

-1

I think you should reconsider the "high overhead" of threads: This overhead is IMHO negligable when compared to the network roundtrips and waiting times on every RPC call. I am pretty sure, that the resources needed to create N threads is marginal against the resources needed to run N networked requests (be they http[s], RPC or whatever).

My approach would be to queue the tasks into a threadsafe collection (ConcurrentQueue, ConcurrentBag and friends), then spawn a finite number of threads looping through those work items until the collection is empty and just end.

This would not only allow you to run the tasks in parallel, it would allow you to run them in a controlled parallel way.

13 Comments

"This overhead is IMHO negligable " - OP is talking about new Thread as opposed to thread pool thread. There is a measureable cost of spawning explicit threads, about 1MB per thread.
Also, these threads are doing nothing useful, whether you use explicit threads or thread pool threads. So in addition to the fact that the overhead is not in fact negligible, they're still not accomplishing anything.
@Eugen Rieck, thanks for your input. I would use this approach as a last resort. However, general guidelines advise against using more than 50 threads per CPU core due to amassing too much thread switching overhead. I have another method, where I scan tens of thousands of IP addresses on our local server network within less than 5 seconds, all in one async call. This is what I am trying to achieve, here but calling the lib static methods continues to force sync execution only.
Another good distinction why I should not use multiple threads but instead multitasking is explained nicely here
@MickyD 1MB per thread is negligable, if you compare it to the 12MB needed for an RPC call over SMB.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.