Как асинхронно вызывать статические методы

2

Я пытаюсь создать простой класс, который вызывает функцию перезагрузки в зависимости от типа машины, которая будет перезагружена. Вызываемые методы ссылаются на библиотеку, которая содержит общедоступные статические методы. Я хочу асинхронно вызывать эти статические методы, используя Task для параллельного вызова методов перезагрузки. Вот код на данный момент:

РЕДАКТИРОВАТЬ После запроса сообщества, это теперь версия того же вопроса, с кодом ниже компиляции. Обратите внимание, что вам нужна библиотека Renci.SshNet, а также необходимо установить ссылки на нее в вашем проекте.

// 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
            }
        }




    }
}

Моя единственная проблема с этой настройкой до сих пор состоит в том, что статические методы вызываются немедленно (последовательно) и не назначаются задаче. Например, строка

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

выполнение занимает 20 секунд, если хост недоступен. Если 10 хостов недоступны, последовательная продолжительность составляет 20 * 10 = 200 секунд.

Мне известны некоторые, казалось бы, похожие вопросы, такие как

Однако приведенные лямбда-выражения по-прежнему оставляют меня с той же ошибкой компилятора ["В этих методах отсутствуют операторы ожидания..."]. Кроме того, я не хочу порождать явные потоки (new Thread (() =>...)) из-за высоких издержек при перезапуске большого количества компьютеров в кластере.

Мне может понадобиться перезагрузить большое количество машин в кластере. Отсюда мой вопрос: как я могу изменить свою конструкцию, чтобы иметь возможность вызывать вышеупомянутые статические методы параллельно?

РЕДАКТИРОВАТЬ Благодаря комментариям @JohanP и @MickyD, я хотел бы пояснить, что я действительно пытался написать асинхронную версию обоих статических методов. Тем не менее, это приводит меня к кроличьей норе, где каждый раз, когда статический метод вызывается в асинхронном методе, я получаю предупреждение компилятора о том, что вызов будет синхронным. Вот пример того, как я пытался обернуть вызов метода как асинхронную задачу, надеясь вызвать зависимые методы асинхронным способом.

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);
    });

}

Есть ли способ обернуть вызов статического метода так, что не нужно переписывать все статические дочерние методы как асинхронные?

Спасибо всем заранее.

  • 0
    Как вы можете запустить несколько методов параллельно? Запустив их в разных темах. Не любите темы? Перепишите методы, которые должны быть async и ожидайте их.
  • 1
    Этот код даже не скомпилируется, поэтому нам трудно помочь вам, поскольку это явно не то, как выглядит ваш код
Показать ещё 1 комментарий
Теги:
asynchronous

3 ответа

0
Лучший ответ

Всем спасибо за ваш вклад и вашу помощь. Я поиграл с ним за последние пару дней и нашел следующий способ асинхронного вызова статического метода:

// 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
            }
        }




    }
}

Эта установка вызывает мои статические методы асинхронно. Я надеюсь, что это помогает кому-то, кто застрял с подобной проблемой. Еще раз спасибо за ваш вклад.

1

Ваш код имеет странную смесь async с продолжениями, и он даже не скомпилируется. Вы должны сделать это async полностью. Когда вы вызываете RebootMachines(...) и этот вызов не может быть await, вы можете запланировать продолжения для этого, то есть 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);

}
  • 1
    Хороший ответ. Единственное, что я хотел бы добавить, это то, что теперь методы являются асинхронными (или вы создали дополнительные асинхронные методы), как правило, рекомендуется переименовывать их с помощью суффикса Async . docs.microsoft.com/en-us/dotnet/csharp/programming-guide/...
  • 0
    Спасибо всем за ваш вклад. Я отредактировал свой вопрос, чтобы показать, что я пробовал в дополнение к простой конструкции кода. По сути, если бы я вызывал методы в новом потоке для каждого вызова, я бы закончил квазиасинхронным выполнением, но с явными издержками переключения потоков.
Показать ещё 1 комментарий
-3

Я думаю, что вам следует пересмотреть "высокую нагрузку" на потоки: эти издержки ИМХО пренебрежимо малы по сравнению с обходами сети и временем ожидания при каждом вызове RPC. Я совершенно уверен, что ресурсы, необходимые для создания N потоков, незначительны по сравнению с ресурсами, необходимыми для выполнения N сетевых запросов (будь то http [s], RPC или что-то еще).

Мой подход заключался бы в том, чтобы поставить задачи в очередь в потокобезопасную коллекцию (ConcurrentQueue, ConcurrentBag и друзья), а затем создать конечное число потоков, проходящих по этим рабочим элементам, пока коллекция не станет пустой и не завершится.

Это не только позволяет запускать задачи параллельно, что позволит запускать их в управляемом параллельно.

  • 0
    «Эти издержки ИМХО незначительны» - OP говорит о new Thread потоке, а не о потоке пула потоков. Существует измеримая стоимость порождения явных потоков, около 1 МБ на поток .
  • 0
    Кроме того, эти потоки не делают ничего полезного , используете ли вы явные потоки или потоки пула потоков. Таким образом , в дополнение к тому , что накладные расходы не на самом деле незначительна, они до сих пор ничего не достижения.
Показать ещё 11 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню