У меня есть требование, чтобы фоновая служба запускала Process
метод каждый день в 0:00 утра.
Итак, один из членов моей команды написал следующий код:
public class MyBackgroundService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public MyBackgroundService(ILogger<MyBackgroundService> logger)
{
_logger = logger;
}
public void Dispose()
{
_timer?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan interval = TimeSpan.FromHours(24);
TimeSpan firstCall = DateTime.Today.AddDays(1).AddTicks(-1).Subtract(DateTime.Now);
Action action = () =>
{
Task.Delay(firstCall).Wait();
Process();
_timer = new Timer(
ob => Process(),
null,
TimeSpan.Zero,
interval
);
};
Task.Run(action);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private Task Process()
{
try
{
// perform some database operations
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
}
return Task.CompletedTask;
}
}
Этот код работает так, как ожидалось. Но мне не нравится, что он синхронно ожидает первого вызова Process
, поэтому поток блокируется и не выполняет никакой полезной работы (поправьте меня, если я ошибаюсь).
Я мог бы сделать асинхронное действие и ожидать в нем вот так:
public Task StartAsync(CancellationToken cancellationToken)
{
// code omitted for brevity
Action action = async () =>
{
await Task.Delay(firstCall);
await Process();
// code omitted for brevity
}
Но я не уверен, что использование Task.Run
здесь хорошо, поскольку метод Process
должен выполнять некоторые операции ввода-вывода (запрашивать базу данных и вставлять некоторые данные), и потому что не рекомендуется использовать Task.Run
в среде ASP.NET.
Я провел рефакторинг StartAsync
следующим образом:
public async Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan interval = TimeSpan.FromHours(24);
TimeSpan firstDelay = DateTime.Today.AddDays(1).AddTicks(-1).Subtract(DateTime.Now);
await Task.Delay(firstDelay);
while (!cancellationToken.IsCancellationRequested)
{
await Process();
await Task.Delay(interval, cancellationToken);
}
}
И это позволяет мне вообще не использовать таймер в MyBackgroundService
.
Должен ли я использовать первый подход с таймером + Task.Run или второй с циклом while + Task.Delay?
Process
займет 5 минут, ваше расписание будет искажаться на 5 минут при каждом выполнении. То есть, пока ваша служба не будет перезапущена. - person Lasse V. Karlsen   schedule 24.10.2020