Многопоточность. Лучший способ

195
02 ноября 2018, 02:20

Стоит задача сделать программу мониторинга железок по SNMP(не спрашивайте зачем, сам в шоке) Планирую использовать c# и библиотеку SNMP lex. Мониториться могут много железок, сотня, а то и больше. Каждая железка имеет кучу параметров для мониторинга.

Хотел сделать так. Мониторинг каждого параметра запускается в отдельном Task в бесконечном цикле и параметр опрашивается каждые 5 секунд.

Выглядит это ужасно, поскольку 100 железок на 20 параметров, итого 2000 тасков с бесконечным циклом.

Посоветуйте, как лучше решить задачу?

Answer 1

Предположим, что вы будете запрашивать информацию асинхронно.
Например, так

static Random r = new Random();
public Task<int> GetDeviceParameter(int deviceId, int parameterId)
{
  return Task.Delay(1000).ContinueWith(t=>r.Next());
}

Я предположил, что запрос занимает 1 секунду. Теперь запросим 20 параметров по конкретному девайсу

public Task<int[]> ReadDeviceParameters(int decviceId)
{
  var tasks = new Task<int>[20];
  for(var i=0; i<20; i++)
  {
    tasks[i] = GetDeviceParameter(decviceId, i);
  }
  return Task.WhenAll(tasks);
} 

Все вроде просто. Запросим это для 100 девайсов

public Task<int[][]> ReadParameters()
{
  var tasks = new Task<int[]>[100];
  for (var i = 0; i < 100; i++)
  {
    tasks[i] = ReadDeviceParameters(i);
  }
  return Task.WhenAll(tasks);
}

Теперь, как оранизовать цикл? Нам, наверное, надо иметь токен отмены для цикла и засекать время, чтобы цикл запускался каждые 5 секунд, но после отработки предыдущих запросов.

public async Task ReadParametersLoop(CancellationToken token)
{
  while(!token.IsCancellationRequested) 
  { 
    var start = DateTime.UtcNow;
    Console.WriteLine($"Starting read at {start:mm:ss}");
    await ReadParameters(); 
    var end = DateTime.UtcNow;
    var diff = end - start;
    var delay = (int)Math.Min(5000, 5000 - diff.TotalMilliseconds);
    Console.WriteLine($"Reading ens at {end:mm:ss}, took {diff}, delay for {delay}");
    if (delay > 0)
      await Task.Delay(delay);
  }
}

Запускаю это в консоли на 15 секунд

var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(15));
await ReadParametersLoop(cts.Token);

Получаю результат

Starting read at 29:56
Reading ens at 29:57, took 00:00:01.0061006, delay for 3993
Starting read at 30:01
Reading ens at 30:02, took 00:00:01.0161016, delay for 3983
Starting read at 30:06
Reading ens at 30:07, took 00:00:01.0161016, delay for 3983

Таким образом, если запросы отрабатывают меньше, чем за 5 секунд (в сумме), то они стабильно запрашиваются каждые 5 секунд.

Answer 2

Вы можете воспользоваться реактивными расширениями System.Reactive

Небольшой пример как работать с мониторингом параметров используя IObservable Вместо Dictionary с параметрами, можно создать отдельный класс.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"Start: {DateTime.Now}");
        var parameters = Enumerable.Range(0, 100).Select(x => $"Parameter {x}").ToArray();
        var devices = Enumerable.Range(0, 20).Select(x => $"Device {x}").ToArray();
        foreach (var device in devices)
        {
            var monitor = Observable.FromAsync(() => GetParametersAsync(parameters))
            .Delay(TimeSpan.FromSeconds(5))
            .Repeat();
            monitor.Subscribe(x => HandleParameters(device, x));
        }
        Console.ReadLine();
    }
    /// <summary>
    /// Пример обработки параметров
    /// </summary>
    private static void HandleParameters(string device, Dictionary<string, string> parameters)
    {
        foreach (var parameter in parameters)
        {
            Console.WriteLine($"{device} {parameter.Key}: {parameter.Value}");
        }
        Console.WriteLine();
    }
    private static async Task<Dictionary<string, string>> GetParametersAsync(string[] parameterIds)
    {
        var parameters = parameterIds.Select(parameterId => new { Id = parameterId, Task = GetParameterAsync(parameterId) }).ToList();
        var tasks = parameters.Select(x => x.Task).ToArray();
        await Task.WhenAll(tasks);
        var results = new Dictionary<string, string>();
        foreach (var parameter in parameters)
        {
            results.Add(parameter.Id, parameter.Task.Result);
        }
        return results;
    }
    /// <summary>
    /// Эмуляция получения данных
    /// </summary>
    public static async Task<string> GetParameterAsync(string parameterId) 
    {
        await Task.Delay(3000);
        return DateTime.Now.ToString();
    }
}
READ ALSO
Динамические данные с IoC контейнером

Динамические данные с IoC контейнером

Изучаю тему IoC и DIВсё вроде понял зачем как и почему пока не дошел до IoC контейнера Unity

202
Как считать файл из сетевой папки в Xamarin Android(c#)

Как считать файл из сетевой папки в Xamarin Android(c#)

Например, у меня есть txt файл на удалённом компьютере в его открытой сетевой папкеНужно локально считать этот файл

249
Вопрос про парсер CsQuery

Вопрос про парсер CsQuery

Всех приветствую, вопрос такой, изучаю этот парсер, вот страница, которую я хочу разобратьВот мой код

235
Как из C# удалить переход на другой лист в Word?

Как из C# удалить переход на другой лист в Word?

Соединяю несколькоdocx документов в один

923