37

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.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Eadel
  • 3,797
  • 6
  • 38
  • 43
  • If `Timer` does not want to cooperate with you to "AutoReset - Gets or sets a value indicating whether the Timer should raise the Elapsed event each time the specified interval elapses or only after the first time it elapses." because you refused to read link you've provided than you are out of luck... :) Side note - generally it is better to use external notifications if you need reliable intervals/avoid recycle of process - "https://www.bing.com/search?q=asp.net+repeating+task" should give you some info (or "ASP.Net background tasks"). – Alexei Levenkov May 26 '15 at 15:02
  • @AlexeiLevenkov nice taunt. Thanks for the tips, will explore more carefully next time. However, it does not solve the main issue. – Eadel May 26 '15 at 15:14
  • Since it does not address main issue it is a comment... Answer by i3arnon gives you links how to call async in fire and forget manner (also known as "call async method from event handler")... – Alexei Levenkov May 26 '15 at 15:43

3 Answers3

35

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

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 3
    Should I write "await PeriodicFooAsync" or just "var result = PeriodicFooAsync"? Do we need await in this case? – Saber Oct 10 '16 at 22:03
  • 2
    @Arvand it depends if you want to wait for the operation to complete or not. `result` in your case will only hold the task, the next line after that will happen immediately. `await` will "block" asynchronously until the periodic task will be cancelled. – i3arnon Oct 10 '16 at 22:05
  • 2
    @Arvand you probably want to save that task somewhere without awaiting it (assuming it lives forever until your application shuts down) and then you can await it before closing. – i3arnon Oct 10 '16 at 22:06
  • 1
    Since I have multiple PeriodicAsycMethods I should certainly save the task and avoid using await, Thank you. – Saber Oct 10 '16 at 22:08
  • 1
    Took me a while to understand how to write it in a general way to accept any task and run it periodiclly, I actually got help from your other answer http://stackoverflow.com/questions/27918061/passing-a-task-as-parameter. Maybe you can update your answer so others won't go through the same problem. @i3arnon – Saber Dec 18 '16 at 18:01
  • 14
    That's not periodic as `await FooAsync` will have a non zero execution time. For example, if `FooAsync` takes 500ms and runs every hour than you will be off by 10 minutes in less than 2 months. – tymtam Nov 06 '17 at 02:04
  • @mayu that's the point. Imagine you run it every 5 minutes and it takes 7. It doesn't need to align with a clock (it doesn't start aligned to begin with). It needs to happen every x interval without running over itself. – i3arnon Nov 06 '17 at 07:21
  • 1
    @i3arnon Of course it solves the OP's problem. I'm just pointing out that it is not very regular. It doesn't run "every n hours/minutes/seconds". – tymtam Nov 06 '17 at 21:27
  • 1
    @mayu I understand. I'm saying It shouldn't. If you run at a tight interval (which you probably can't guarantee accurately anyway) you risk your action running over the interval and introducing unintentional cascading concurrency. That may cause operations to run even slower which causes more concurrency, etc. That's a bad practice. – i3arnon Nov 06 '17 at 23:12
  • Even though the use of "async" and "await" is a "trendy" and "cool" approach, You have to keep in mind that async doesn't run Your task in a separate thread, it just defers the execution like javascript does. Please consider to use Threads if You'll run in situations where performance is important and You face a limit situation of many resources consumed by the system. – Claudio Ferraro Jul 30 '19 at 10:28
  • 1
    Suggestion for improvement: You could have a more consistent interval by creating the `Task.Delay` task before awaiting the `FooAsync` and awaiting it afterwards. This will make a difference if the `FooAsync` does some non-trivial work synchronously before returning a `Task`. – Theodor Zoulias Jun 30 '20 at 17:50
  • @TheodorZoulias That's intentional. Imagine what happens if `FooAsync` takes longer than the delay interval for some reason.. – i3arnon Jul 01 '20 at 20:56
  • If the awaiting of `FooAsync` takes longer than the `interval`, then with my suggestion the period will be extended and essentially defined by the duration of `FooAsync`. In that case a more precise name for the `interval` argument would be `minInterval`. Btw the current version of `PeriodicFooAsync` is not periodic. It's not trying to sustain a constant period between each invocation of the `FooAsync`. Instead it just imposes a delay between finishing one operation and starting the next. – Theodor Zoulias Jul 01 '20 at 21:35
  • @TheodorZoulias again, that's intentional. Your suggestion (in that case) will cause a constant tight (`while (true)`) loop, which is almost always a bad idea. If you require a exact cadence the solution should be much more robust. – i3arnon Jul 02 '20 at 07:11
24

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.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • hmm. would this be more or less performant than using a timer do you recon? – sommmen Jul 14 '20 at 10:22
  • 1
    @sommmen I believe that performance-wise should be equal, because both `System.Timers.Timer` and `Task.Delay` are using a [`System.Threading.Timer`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.timer) internally. – Theodor Zoulias Jul 14 '20 at 12:01
  • Do we ever exit the loop with `while (true)` when `cancellationToken` is signaled? – Kuba Wyrostek Sep 20 '22 at 08:50
  • 1
    @KubaWyrostek yes, the loop exits. When the `cancellationToken` is canceled, the task returned from the `Task.Delay` or the `PeriodicTimer.WaitForNextTickAsync` method completes immediately in a canceled state, resulting in an `OperationCanceledException`. – Theodor Zoulias Sep 20 '22 at 10:29
  • Hmm... sounds like an exception based flow control. Isn't this an anti-pattern? – Kuba Wyrostek Sep 20 '22 at 18:37
  • 1
    @KubaWyrostek in general it's an anti-pattern, but cancellation does not abide by this rule. It was decided early on that cancellation is something exceptional and should be communicated through exceptions, and now there are tens of thousands of .NET APIs that follow this pattern. The ship has sailed long ago on this issue. If you decide to break the convention and don't throw, then at least rename the parameter from `cancellationToken` to `stoppingToken`, so that the consumers of your API have a clue about what to expect. – Theodor Zoulias Sep 20 '22 at 19:08
  • I get it. The parameter for `BackgroundService.ExecuteAsync` is named exactly that (`stoppingToken`), so if I were to use this periodic timer approach within such a service - I should swallow the exception, right? Reference: https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.backgroundservice.executeasync – Kuba Wyrostek Sep 21 '22 at 06:08
  • @KubaWyrostek I've never worked with the `BackgroundService` class. My expectation is that throwing or not from the `ExecuteAsync` should be the same. An `OperationCanceledException` is probably going to be ignored. – Theodor Zoulias Sep 21 '22 at 06:49
  • @TheodorZoulias so if we have to observe the OperationCanceledException, what's the point of returning a bool by WaitForNextTickAsync() and this `while periodicTimer.WaitForNextTickAsync(cancellingToken)` pattern? Shouldn't it just return "false" in order to cleanly exit loop? – Endrju Jan 18 '23 at 11:58
  • 1
    @Endrju reacting to a cancellation signal by throwing an exception is how things work in .NET. You might think that it's messy, and I can understand it, but if you do otherwise most people will consider your API to be surprising and atypical, not clean. You won't gain style points by communicating cancellation with boolean flags. – Theodor Zoulias Jan 18 '23 at 12:09
11

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

public async Task StartTimer(CancellationToken cancellationToken)
{

   await Task.Run(async () =>
   {
      while (true)
      {
          DoSomething();
          await Task.Delay(10000, cancellationToken);
          if (cancellationToken.IsCancellationRequested)
              break;
      }
   });

}

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

cancellationToken.Cancel();
Abhishek
  • 3,337
  • 4
  • 32
  • 51
Guy Levin
  • 1,230
  • 1
  • 10
  • 22
  • 5
    1. No need for `Task.Run`. 2. DoSomething should be async. 3. You should await the task returned form `Task.Delay` – i3arnon May 26 '15 at 15:06
  • 1
    This code throws `System.Threading.Tasks.TaskCanceledException` on `await Task.Delay(...)` line. – pitersmx Jun 28 '17 at 17:51
  • 12
    That's not periodic as DoSomething will have a non zero execution time. For example, if DoSomething takes 500ms and runs every hour than you will be off by 10 minutes in less than 2 months. – tymtam Nov 06 '17 at 02:05
  • 1
    @tymtam if DoSomehthing is async and we DO NOT await for it then it will not add anything to our periodic execution. The only problem would be if DoSomethings takes longer than our periodic check which should be solved with an object lock on DoSomething to avoid re-entry – kuklei Dec 19 '20 at 11:55
  • About the task TaskCanceledException that @pitersmx mentions, this is the default behaviour of the task library and something every task should implement as per this [source](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation). You could either wrap the Task.Delay in a try/catch block or simply ignore the cancellation token on that task – kuklei Dec 19 '20 at 11:58