Run Async Method Regularly with Specified Interval

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

Calling a method in specific interval

You can achieve that in multiple ways. One of the easiest one would be something like this:

public async Task RunScheduleJob(CancellationToken token)
{
while(!token.IsCancellationRequest)
{
YourMethod();
await Task.Delay(TimeSpan.FromHours(1), token)
}
}

PS. For such a simple job you don't need to use Reactive Extensions or any other external library. You can achieve it by just playing around with Task instances

Run async hosted service every 5 minutes in ASP.NET Core

Use BackgroundService instead of IHostedService

public class MyScheduler : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Option 1
while (!stoppingToken.IsCancellationRequested)
{
// do async work
using (var scope = _serviceScopeFactory.CreateScope())
{
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.Execute(stoppingToken);
}
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}

// Option 2 (.NET 6)
var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// do async work
// ...as above
}
}
}

Async sleep in loop

One way of doing it would be to create a Stopwatch and just wait for it to reach the next milestone after doing each operation.

The benefit of this way is that:

  • You will add the overhead of the loop into the mix, adjusting your delay to compensate
  • As such, it should have little to no "drifting" (where operations will get progressively "later" according to when they should've happened)

Sort of like this:

var sw = Stopwatch.StartNew();
long next = 100;
while (sw.ElapsedMilliseconds < 10000)
{
// do your operation here
long left = next - sw.ElapsedMilliseconds;
if (left > 0)
await Task.Delay((int)left);
next += 100;
}

Due to the accuracy of things, your operations will around each 100 milliseconds, but they shouldn't drift.

If you're OK with tying up a thread for the duration of those 10 seconds, you could switch out the delay part with Thread.Sleep:

if (left > 0)
Thread.Sleep((int)left);

It will run your operations closer to the 100 millisecond mark, but might still have a slight overhead due to the fact that Thread.Sleep has some overhead as well.

If you're OK with "burning CPU", you can do this instead:

var sw = Stopwatch.StartNew();
var next = 100;
while (sw.ElapsedMilliseconds < 10000)
{
// do your operation here

while (sw.ElapsedMilliseconds < next) ;
next += 100;
}

This will run your operation even closer to the actual 100 milliseconds mark, as Task.Delay and Thread.Sleep will have overhead that will tend to delay just slightly longer than what you intended. It will, however, tie up a CPU core for the duration of those 10 seconds.



Related Topics



Leave a reply



Submit