Run async method regularly with specified interval

71.8k views Asked by At

I need to publish some data to the service from the C# web application. The data itself is collected when user uses the application (a kind of usage statistics). I don't want to send data to the service during each user's request, I would rather collect the data in the app and send then all the data in a single request in a separate thread, that does not serve the users requests (I mean user does not have to wait for the request to be processed by the service). For this purpose I need a kind of JS's setInterval analog - the launch of the function each X seconds to flush all collected data to the service.

I found out that Timer class provides somewhat similar (Elapsed event). However, this allows to run the method only once, but that's not a big issue. The main difficulty with it is that it requires the signature

void MethodName(object e, ElapsedEventArgs args)

while I would like to launch the async method, that will call the web-service (input parameters are not important):

async Task MethodName(object e, ElapsedEventArgs args)

Could anyone advise how to solve the described task? Any tips appreciated.

3

There are 3 answers

14
i3arnon On BEST ANSWER

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

5
Guy Levin On

The simple way of doing this is using Tasks and a simple loop:

public async Task StartTimer(CancellationToken cancellationToken) =>
   await Task.Run(async () =>
   {
      while (!cancellationToken.IsCancellationRequested)
      {
          DoSomething();
          await Task.Delay(10000, cancellationToken);
      }
   });

When you want to stop the thread just abort the token:

cancellationTokenSource.Cancel();
10
Theodor Zoulias On

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)
    {
        Task 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 PeriodicTimer timer = new(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.

As you can see the PeriodicTimer doesn't guarantee a minimum interval between invocations of the action.

The PeriodicAsync implementations above have been kept simple intentionally for educational purposes. Things like argument validation and ConfigureAwait have been omitted.