Run async method regularly with specified interval
The async
equivalent is a while
loop with Task.Delay
(which internally uses a System.Threading.Timer
):
public async Task PeriodicFooAsync(TimeSpan interval, CancellationToken cancellationToken)
{
while (true)
{
await FooAsync();
await Task.Delay(interval, cancellationToken)
}
}
It's important to pass a CancellationToken
so you can stop that operation when you want (e.g. when you shut down your application).
Now, while this is relevant for .Net in general, in ASP.Net it's dangerous to do any kind of fire and forget. There are several solution for this (like HangFire), some are documented in Fire and Forget on ASP.NET by Stephen Cleary others in How to run Background Tasks in ASP.NET by Scott Hanselman
Here is a method that invokes an asynchronous method in periodic fashion:
public static async Task PeriodicAsync(Func<Task> action, TimeSpan interval,
CancellationToken cancellationToken = default)
{
while (true)
{
var delayTask = Task.Delay(interval, cancellationToken);
await action();
await delayTask;
}
}
The supplied action
is invoked every interval
, and then the created Task
is awaited. The duration of the awaiting does not affect the interval, unless it happens to be longer than that. In that case the principle of no-overlapping-execution takes precedence, and so the period will be extended to match the duration of the awaiting.
In case of exception the PeriodicAsync
task will complete with failure, so if you want it to be error-resilient you should include rigorous error handling inside the action
.
Usage example:
Task statisticsUploader = PeriodicAsync(async () =>
{
try
{
await UploadStatisticsAsync();
}
catch (Exception ex)
{
// Log the exception
}
}, TimeSpan.FromMinutes(5));
.NET 6 update: It is now possible to implement an almost identical functionality without incurring the cost of a Task.Delay
allocation on each loop, by using the new PeriodicTimer
class:
public static async Task PeriodicAsync(Func<Task> action, TimeSpan interval,
CancellationToken cancellationToken = default)
{
using var timer = new PeriodicTimer(interval);
while (true)
{
await action();
await timer.WaitForNextTickAsync(cancellationToken);
}
}
The WaitForNextTickAsync
method returns a ValueTask<bool>
, which is what makes this implementation more efficient. The difference in efficiency is pretty minuscule though. For a periodic action that runs every 5 minutes, allocating a few lightweight objects on each iteration should have practically zero impact.
The behavior of the PeriodicTimer
-based implementation is not identical with the Task.Delay
-based implementation. In case the duration of an action is longer than interval
, both implementations will invoke the next action immediately after the completion of the previous action, but the scheduler of the PeriodicTimer
-based implementation will not slide forward like the Task.Delay
-based implementation does. See the marble diagram below for a visual demonstration of the difference:
Clock X---------X---------X---------X---------X---------X---------X--
Task.Delay: +-----| +---| +------------|+---| +------| +--|
PeriodicTimer: +-----| +---| +------------|+---| +------| +--| +--
The scheduling of the Task.Delay
-based implementation was permanently shifted forward, because the third invocation of the action
lasted longer than the interval
.