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.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
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
Method 2
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();
Method 3
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
.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0