Асинхронная пинговалка C#
Мне нужно сделать пинговалку на C#, но возник вопрос. У меня огромное кол-во объектов для пингования, примерно 2000, и естественно я их не буду пинговать в 1 потоке и без асинхронности. У пинговалки есть таймер, который запускает метод каждые N секунд, а метод который таймер запустил запускает асинхронный метод через Task.Run. И желательно чтобы созданный поток таймером успел все обработать (если объект оффлайн, то просто пометим оффлайн, поставя таймаут для пинги). Но дело такое, что я сделал всё это, и с помощью класса Ping начал отправлять пакеты (await ping.SendPingAsync(addr, timeout)). Но если я это делаю в потоке порожденном таймером, то мы останавливаемся на объекте который оффлайн, и выходим из метода, возвращая поток в пул. Но нужно чтобы поток просто отправлял на все объекты пинг, не останавливая и не ждя, и после всех операций получал ответ (можно какой нить event использовать, например PingCompleted). Как это можно сделать?
Сам метод который всё пингует:
protected async override Task UpdateAsync()
{
List<PingObject> pingObjects = await _applicationContext.PingObjects.Where(elem => elem.Enabled).ToListAsync();
_logger?.Log(LogLevel.Information, $"start ping {pingObjects.Count} objects");
foreach (var pingObject in pingObjects)
{
WaitCallback callback = new WaitCallback(async (args) =>
{
using Ping ping = new Ping();
PingReply reply = await ping.SendPingAsync(IPAddress.Parse(pingObject.Address), 5000);
_logger?.Log(LogLevel.Information, $"send ping packet to {pingObject.Address}. ID={pingObject.ID}");
await ReceivePacketAsync(pingObject, reply);
});
ThreadPool.QueueUserWorkItem(callback);
}
}
Собственно класс, где и вызывается UpdateAsync каждые N секунд:
internal abstract class BasePinger
{
public int Period { get; set; }
protected BasePinger(int period)
{
Period = period == 0 ? 10000 : period;
_timer = new System.Timers.Timer(Period);
_timer.Elapsed += Update;
}
public void Start() => _timer.Start();
/// <summary>
/// Метод который будет вызваться таймером каждые N секунд, в нем идет обработка
/// </summary>
/// <returns></returns>
protected abstract Task UpdateAsync();
protected abstract Task ReceivePacketAsync(PingObject pingObject, PingReply pingReply);
private void Update(object? sender, ElapsedEventArgs e) => Task.Run(UpdateAsync);
private System.Timers.Timer _timer;
}
Вообще вся проблема в том, что объекты моментально могут отвечать, в следствии чего мы порождаем большое кол-во потоков (нагружая тем самым цп). И да, я знаю что ThreadPool.QueueUserWorkItem берет потоки из пула или порождает их, но я пока не знаю как выкрутится и сделать всё в 1 потоке
Ответы (1 шт):
- Прочитать, как вообще работает с асинхронность - Thread'ы, Task'и, async'и, await'ы в C# под WPF на .NET Framework 4.8
- Избавиться от таймера. Если уж вы начали писать асинхронный код, пишите его асинхронным полностью, используйте токены отмены для прерываний операций в случае необходимости - Создание бесконечно повторяющегося метода в c#
- Запускать 20000000000 задач одновременно, даже асинхронных - штука неблагодарная и будет грузить проц даже если асинхронный код написан самым лучшим образом. Так же есть сетевые ограничения, у компа сокетов, чтобы открыть и выполнить сетевые запросы, не так уж много. Я рекомендую одновременно выполнять не более, чем
N * 2операций, где N - количество ядер процессора - Массовые асинхронные вызовы с ограничением на количество параллельных без семафора, множитель уже подрегулируйте по вкусу при испытаниях кода
Далее, вынести из метода пингования те части, которые не требуют изменения полученных данных.
await _applicationContext.PingObjects.Where(elem => elem.Enabled).ToListAsync();
Зачем вы каждую секунду повторяете этот запрос? Данные меняются прямо во время выполнения операции? Выполняйте каждую секунду только то, что нужно, а что не нужно - выполняйте 1 раз.
Вот очень простой пример, как асинхронные операции работают без явного выделения потока, одновременно.
static async Task Main(string[] args)
{
Task[] tasks = new Task[4];
Console.WriteLine("ping start");
for (int i = 0; i < 4; i++)
{
tasks[i] = DummyPing(IPAddress.Parse($"1.2.3.{i + 1}"));
}
await Task.WhenAll(tasks);
Console.WriteLine("ping end");
Console.ReadKey();
}
static async Task DummyPing(IPAddress ip)
{
Console.WriteLine($"pinging {ip}");
await Task.Delay(100);
Console.WriteLine($"ping ok {ip}");
}
Вывод в консоль
ping start
pinging 1.2.3.1
pinging 1.2.3.2
pinging 1.2.3.3
pinging 1.2.3.4
ping ok 1.2.3.4
ping ok 1.2.3.1
ping ok 1.2.3.3
ping ok 1.2.3.2
ping end
Обратите внимание на порядок вывода строк, запуск методов происходит в строго синхронном порядке, а вот завершение - как повезет.