Хороший подход к созданию временной асинхронной службы в C # - PullRequest
0 голосов
/ 17 мая 2018

Мне нужно создать сервис, который будет работать теоретически навсегда.

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

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

Я придумал два способа сделать это

  1. Используйте System.Timers.Timer для отправки событий, а затем запускайте код при возникновении события

  2. Создайте метод, который будет вызывать себя, используя Task.Delay

Вот несколько примеров того, что я имею в виду под этими двумя методами:

Таймер

public void Run()
{
    System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromSeconds(2).TotalMilliseconds);
    CancellationTokenSource ts = new CancellationTokenSource();

    timer.Elapsed += async (sender, argz) => await CodeExecutor(ts.Token);
    timer.Start();
}

public async Task CodeExecutor(CancellationToken token)
{
    //Some logic
}

Task.Delay

private async Task RepeatableMethod(CancellationToken cancellationToken)
{
    try
    {
        //some logic
    }
    catch (Exception e)
    {
    }
    finally
    {
        await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken)
            .ContinueWith(_ => RepeatableMethod(cancellationToken),cancellationToken);
    }
}

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

Что будет считаться лучшим подходом в следующем сценарии, и есть ли другой (хороший) способ, которым я могу подойти к своему делу?

Ответы [ 3 ]

0 голосов
/ 17 мая 2018

Грубый асинхронный таймер в приведенной ниже форме, безусловно, возможен:

async Task Timer(Timespan interval, Func<Task> someCallback, CancellationToken token)
{
     while (!token.IsCancellationRequested)
     {
        await Task.Delay(interval, token);
        await someAction(); // Or someAction?.Invoke();
     }
}

Однако есть несколько вещей, на которые следует обратить внимание:

  • На практике, используя Task.Delayне так точен, как System.Threading.Timer, поскольку необходимо запланировать выполнение продолжения - При сценариях с высокой нагрузкой я обнаружил, что точность часто может быть хуже 15 мс.
  • Время, затраченное на обратный вызов (someAction), необходимо учитывать.Если это не тривиально, вам может понадобиться добавить StopWatch вокруг обратного вызова, а затем вычесть время, затраченное на обратный вызов, из следующего Task.Delay, если вы хотите достаточно точную частоту обратного вызова.
  • Вынужно беспокоиться о повторном входе.Если обратный вызов занимает больше времени, чем задержка, то ваш таймер будет ограничен.В этом случае вам может потребоваться удалить await в обратном вызове (если он асинхронный), а в худшем случае, если в обратном вызове имеется значительная синхронная задержка, вам также может потребоваться запустить обратный вызов в своем собственном потоке (например, Task.Run(() => Callback())) для обеспечения повторного входа таймера, возможно, существует риск нестабильной загрузки обратного вызова.
  • Возможно, вам также потребуется рассмотреть возможность добавления тайм-аута для обратного вызова с помощью механизма вот так
  • И, конечно, обратный вызов должен быть заключен в обработчик исключений, а затем принять решение о том, что делать, если что-то пойдет не так.Повторные попытки, вероятно, не нужны, так как вызов будет повторен в следующий интервал.

Мне, как правило, я бы остался с решением на основе System.Threading.Timer - оно более точным и повторно входит , и большое количество таймеров может быть создано в любом случае одновременно, так что, похоже, мало что подходит для Task.Delay таймера.

0 голосов
/ 17 мая 2018

Насколько я вижу, это выглядит как производитель / потребитель .

Вы можете использовать что-то вроде BlockingCollection для такого сценария, но это не асинхронно, это означает, что в этом случае потребуется выделенный поток, ожидающий элементы.В его библиотеке AsyncEx есть асинхронная реализация Стивена Клири, которая может обеспечить аналогичную функциональность.Более подробную информацию об этом можно найти в его блоге .

. Или, возможно, TPL Dataflow подойдет, проверьте этот пример .

0 голосов
/ 17 мая 2018

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

...