Выдает .NET Core IHostedService Задача была немедленно отменена

Я пытаюсь встроить службу в свое приложение .NET Core 3.1, которое работает ежечасно. Моя служба запускается каждый час (т. е. достигает точки останова в StartAsync()), но реализация немедленно выдает следующую ошибку:

Задача была отменена

Я искал довольно много, однако мне трудно найти полезные ответы; кажется, что они чаще всего возникают при использовании HttpClient, что не имеет отношения к моему случаю.

Тем не менее, у меня есть подозрение, что я должен ожидать чего-то, но мои попытки добавить await привели к зависанию приложения при запуске (я полагаю, что непреднамеренно ждал полной часовой итерации...?) . В учебнике, которому я следовал, нигде не используйте await; за исключением недосмотра (очень возможно), мой код кажется идентичным показанному примеру.

Я вытащил кишки ExecuteAll(), чтобы подтвердить код в работах.

Спасибо.

public Task StartAsync(CancellationToken cancellationToken)
{
  TimeSpan interval = TimeSpan.FromHours(1);
  var nextRunTime = DateTime.Today.AddHours(DateTime.UtcNow.Hour + 1);

  var curTime = DateTime.UtcNow;
  var firstInterval = nextRunTime.Subtract(curTime);

  //Run once on startup
  TriggerHourlyService(null);
  void action()
  {
    //Schedule the first iteration
    var t1 = Task.Delay(firstInterval);
    t1.Wait();
    TriggerHourlyService(null);

    //Schedule hourly
    _timer = new Timer(
      TriggerHourlyService,
      null,
      TimeSpan.Zero,
      interval
    );
  }

  Task.Run(action);
  return Task.CompletedTask;
}
private void TriggerHourlyService(object state)
{
  using var scope = _serviceScopeFactory.CreateScope();
  IHourlyService hourlyService = scope.ServiceProvider.GetRequiredService<IHourlyService>();

  //ASYNC METHOD
  hourlyService.ExecuteAll();
}

Обновлять

После некоторых заметок из комментариев я немного переписал код, чтобы отойти от учебника (похоже, он продвигал некоторые опасные методы). Однако с этим переписыванием мое приложение зависает при запуске. Я считаю, что проблема связана с await Task.Delay(...), но у меня сложилось впечатление, что это не блокирует.

public async Task StartAsync(CancellationToken cancellationToken)
{
    TimeSpan interval = TimeSpan.FromHours(1);
    var nextRunTime = DateTime.UtcNow.Date.AddHours(DateTime.UtcNow.Hour + 1);

    var curTime = DateTime.UtcNow;
    var firstInterval = nextRunTime.Subtract(curTime);

    //Run once on startup
    await TriggerHourlyService(null);

    //First run on next hour
    await Task.Delay(firstInterval);
    await TriggerHourlyService(null);

    //Schedule hourly
    _timer = new Timer(
        async (state) => await TriggerHourlyService(state),
        null,
        TimeSpan.Zero,
        interval
    );
}
private async Task TriggerHourlyService(object state)
{
    using var scope = _serviceScopeFactory.CreateScope();
    IHourlyService hourlyService = scope.ServiceProvider.GetRequiredService<IHourlyService>();
    await hourlyService.ExecuteAll();
}

person Santi    schedule 26.04.2021    source источник
comment
Расскажите мне больше о вашей службе. У вас есть клиентское приложение, подключающееся к службе. Подскажите, как клиент общается с сервисом? У вас есть запрос и ответ. Ответ: ‹IHourlyService›   -  person jdweng    schedule 27.04.2021
comment
Не могли бы вы уточнить или более прямо сказать, что вы предлагаете? Я чувствую, что есть какой-то завуалированный совет, который я не улавливаю.   -  person Santi    schedule 27.04.2021
comment
Я не хочу с вами не соглашаться. Вы сказали, что у вас нет HttpClient. У вас есть клиент, который общается с сервером. Итак, есть протокол, который используется для связи. Это может быть что угодно, но в большинстве случаев в наши дни это HTTP. Поэтому мне нужно больше информации о протоколе.   -  person jdweng    schedule 27.04.2021
comment
IHostedService — это стандартная зависимость с ограниченной областью действия в .NET Core. Он запускается при запуске приложения до того, как будет настроен конвейер обработки запросов приложения, отсюда и мое замешательство. Кроме того, чтобы уточнить, когда я сказал HttpClient, я имел в виду класс HttpClient - эта задача была отменена, исключение кажется общим с тайм-аутами. Я ценю вашу помощь!   -  person Santi    schedule 27.04.2021
comment
Если этот метод hourlyService.ExecuteAll(); не блокирует, то ваш поставщик услуг и все, что им создано, удаляется до того, как что-либо внутри этого метода будет запущено. Кроме того, ваш код может зайти в тупик, вызвав: t1.Wait();. Вы должны перестроить эту часть, чтобы вы могли должным образом await ее.   -  person Silvermind    schedule 27.04.2021
comment
@Santi -- IHostedService вводится как переходный.   -  person Andy    schedule 27.04.2021
comment
@Энди, ты абсолютно прав, спасибо. Я неправильно прочитал документацию; в нем говорилось, что пример активировал службу с ограниченной областью действия, а не ссылался на саму IHostedService.   -  person Santi    schedule 27.04.2021
comment
В этом коде очень много подводных камней. Вы должны эмулировать пример Microsoft. Вот другой пример.   -  person Andy    schedule 27.04.2021
comment
@Энди, спасибо. В соответствии с предложением Silvermind выше я начал переписывать его, чем я сейчас поделился в вопросе, однако, очевидно, я все еще не понимаю. Мой такой же, как пример Microsoft, которым вы поделились, с одним небольшим дополнением планирования одноразового запуска (Task.Delay реализует Timer из того, что я читаю), чтобы настроить его на час. Но теперь приложение зависает при запуске.   -  person Santi    schedule 27.04.2021
comment
Каждая размещенная служба запускается в определенном порядке, поскольку ваш метод StartAsync никогда не завершается, узел никогда не запускается. Если вы хотите, чтобы служба работала долго, используйте BackgroundService.   -  person Jeremy Lakeman    schedule 27.04.2021
comment
Какие у вас коммуникации? Отправляет ли сервер сообщение периодически или клиент отправляет запрос, а затем возвращает ответ. Возможно, вам нужно что-то отправить, прежде чем вы что-то получите.   -  person jdweng    schedule 27.04.2021
comment
@JeremyLakeman Хороший звонок! Я только что использовал свой обновленный код в BackgroundService, и, похоже, он работает! Если вы хотите опубликовать это как ответ, сделайте это, и я отмечу это.   -  person Santi    schedule 27.04.2021
comment
@Santi - ваш код не похож на пример Microsoft. Конечно, вы используете таймер. но эта локальная функция, а затем так, как вы ее называете... используя Task.Run, не дожидаясь ее результатов. Как я уже сказал, ваш код содержит много ловушек и демонстрирует непонимание основных асинхронных шаблонов.   -  person Andy    schedule 27.04.2021
comment
@ Энди Вы имеете в виду мой обновленный код или исходный код ...? Я переписал его, чтобы устранить некоторые ловушки и сделать его более похожим на Использование сервиса с заданной областью в примере фоновой задачи. Он не использует Task.Run или .Wait(). Все ожидается и т.д. Единственное, что не отражено в моем обновлении, это изменение с IHostedService на BackgroundService, которое я с тех пор сделал.   -  person Santi    schedule 27.04.2021
comment
Кроме того, не могли бы вы пояснить, что не так с этой локальной функцией? Я буквально вытащил это строка за строкой из примера Microsoft, который я только что связал. Именно из-за непонимания основных асинхронных паттернов я и задал этот вопрос. Я многому научился только из этих комментариев.   -  person Santi    schedule 27.04.2021


Ответы (1)


IHostedServce отлично подходит, если вы хотите запускать код во время запуска и остановки вашей основной программы. Каждая определенная вами служба будет запускаться и останавливаться в том порядке, в котором вы их определили. Но если ваш метод запуска никогда не завершится, хост-приложение никогда не запустится.

Если все, что вам нужно, это длинный рабочий цикл, BackgroundService позаботится о времени жизни запуска / остановки за вас.

public class MyService : BackgroundService{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken){
        while(!stoppingToken.IsCancellationRequested){
            await Task.Delay(...., stoppingToken);
            //
        }
    }
}

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

person Jeremy Lakeman    schedule 27.04.2021