Run async method regularly with specified interval
Solution 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
Solution 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();
Solution 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
.
Related videos on Youtube
Eadel
Updated on February 13, 2022Comments
-
Eadel about 2 years
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 signaturevoid 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.
-
Alexei Levenkov almost 9 yearsIf
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 - "bing.com/search?q=asp.net+repeating+task" should give you some info (or "ASP.Net background tasks"). -
Eadel almost 9 years@AlexeiLevenkov nice taunt. Thanks for the tips, will explore more carefully next time. However, it does not solve the main issue.
-
Alexei Levenkov almost 9 yearsSince 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")...
-
-
i3arnon almost 9 years1. No need for
Task.Run
. 2. DoSomething should be async. 3. You should await the task returned formTask.Delay
-
Saber over 7 yearsShould I write "await PeriodicFooAsync" or just "var result = PeriodicFooAsync"? Do we need await in this case?
-
i3arnon over 7 years@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 over 7 years@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.
-
Saber over 7 yearsSince I have multiple PeriodicAsycMethods I should certainly save the task and avoid using await, Thank you.
-
Saber over 7 yearsTook 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 stackoverflow.com/questions/27918061/…. Maybe you can update your answer so others won't go through the same problem. @i3arnon
-
pitersmx almost 7 yearsThis code throws
System.Threading.Tasks.TaskCanceledException
onawait Task.Delay(...)
line. -
tymtam over 6 yearsThat's not periodic as
await FooAsync
will have a non zero execution time. For example, ifFooAsync
takes 500ms and runs every hour than you will be off by 10 minutes in less than 2 months. -
tymtam over 6 yearsThat'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.
-
i3arnon over 6 years@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.
-
tymtam over 6 years@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".
-
i3arnon over 6 years@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.
-
Claudio Ferraro almost 5 yearsEven 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.
-
Theodor Zoulias almost 4 yearsSuggestion for improvement: You could have a more consistent interval by creating the
Task.Delay
task before awaiting theFooAsync
and awaiting it afterwards. This will make a difference if theFooAsync
does some non-trivial work synchronously before returning aTask
. -
i3arnon almost 4 years@TheodorZoulias That's intentional. Imagine what happens if
FooAsync
takes longer than the delay interval for some reason.. -
Theodor Zoulias almost 4 yearsIf the awaiting of
FooAsync
takes longer than theinterval
, then with my suggestion the period will be extended and essentially defined by the duration ofFooAsync
. In that case a more precise name for theinterval
argument would beminInterval
. Btw the current version ofPeriodicFooAsync
is not periodic. It's not trying to sustain a constant period between each invocation of theFooAsync
. Instead it just imposes a delay between finishing one operation and starting the next. -
i3arnon almost 4 years@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. -
sommmen almost 4 yearshmm. would this be more or less performant than using a timer do you recon?
-
Theodor Zoulias almost 4 years@sommmen I believe that performance-wise should be equal, because both
System.Timers.Timer
andTask.Delay
are using aSystem.Threading.Timer
internally. -
kuklei over 3 years@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 over 3 yearsAbout 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. You could either wrap the Task.Delay in a try/catch block or simply ignore the cancellation token on that task