102

I'm new to .Net 4.0's Tasks and I wasn't able to find what I thought would be a Task based replacement or implementation of a Timer, e.g. a periodic Task. Is there such a thing?

Update I came up with what I think is a solution to my needs which is to wrap the "Timer" functionality inside a Task with child Tasks all taking advantage of the CancellationToken and returns the Task to be able to participate in further Task steps.

public static Task StartPeriodicTask(Action action, int intervalInMilliseconds, int delayInMilliseconds, CancellationToken cancelToken)
{ 
    Action wrapperAction = () =>
    {
        if (cancelToken.IsCancellationRequested) { return; }

        action();
    };

    Action mainAction = () =>
    {
        TaskCreationOptions attachedToParent = TaskCreationOptions.AttachedToParent;

        if (cancelToken.IsCancellationRequested) { return; }

        if (delayInMilliseconds > 0)
            Thread.Sleep(delayInMilliseconds);

        while (true)
        {
            if (cancelToken.IsCancellationRequested) { break; }

            Task.Factory.StartNew(wrapperAction, cancelToken, attachedToParent, TaskScheduler.Current);

            if (cancelToken.IsCancellationRequested || intervalInMilliseconds == Timeout.Infinite) { break; }

            Thread.Sleep(intervalInMilliseconds);
        }
    };

    return Task.Factory.StartNew(mainAction, cancelToken);
}      
Jim
  • 4,910
  • 4
  • 32
  • 50
  • 7
    You should use a Timer inside the Task instead of using Thread.Sleep mechanism. It's more efficient. – Yoann. B Mar 08 '11 at 12:13

7 Answers7

99

It depends on 4.5, but this works.

public class PeriodicTask
{
    public static async Task Run(Action action, TimeSpan period, CancellationToken cancellationToken)
    {
        while(!cancellationToken.IsCancellationRequested)
        {
            await Task.Delay(period, cancellationToken);

            if (!cancellationToken.IsCancellationRequested)
                action();
        }
     }

     public static Task Run(Action action, TimeSpan period)
     { 
         return Run(action, period, CancellationToken.None);
     }
}

Obviously you could add a generic version that takes arguments as well. This is actually similar to other suggested approaches since under the hood Task.Delay is using a timer expiration as a task completion source.

Jeff
  • 2,701
  • 2
  • 22
  • 35
  • 1
    I switched to this approach just now. But I conditionally call `action()` with a repeat of `!cancelToken.IsCancellationRequested`. That's better, right? – HappyNomad Jul 04 '15 at 08:40
  • 3
    Thanks for this - we're using the same but have moved the delay until after the action (it makes more sense to us as we need to call the action immediately then repeat after x) – Michael Parker Oct 20 '16 at 15:00
  • 3
    Thanks for this. But this code won't run "every X hours" it will run "every X hours + time of `action` execution" am I right? – Alex from Jitbit Nov 01 '17 at 22:51
  • Correct. You would need some math if you want to account for execution time. However that can get tricky if execution time exceeds your period, etc... – Jeff Nov 02 '17 at 01:50
  • If I want multiple of periodic task running in parallel, do I just use `Parallel.Invoke()` to execute them? – Peter Nov 08 '21 at 01:17
62

UPDATE I am marking the answer below as the "answer" since this is old enough now that we should be using the async/await pattern. No need to downvote this anymore. LOL


As Amy answered, there is no Tasked based periodic/timer implementation. However, based upon my original UPDATE, we have evolved this into something quite useful and production tested. Thought I would share:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication7
{
    class Program
    {
        static void Main(string[] args)
        {
            Task perdiodicTask = PeriodicTaskFactory.Start(() =>
            {
                Console.WriteLine(DateTime.Now);
            }, intervalInMilliseconds: 2000, // fire every two seconds...
               maxIterations: 10);           // for a total of 10 iterations...

            perdiodicTask.ContinueWith(_ =>
            {
                Console.WriteLine("Finished!");
            }).Wait();
        }
    }

    /// <summary>
    /// Factory class to create a periodic Task to simulate a <see cref="System.Threading.Timer"/> using <see cref="Task">Tasks.</see>
    /// </summary>
    public static class PeriodicTaskFactory
    {
        /// <summary>
        /// Starts the periodic task.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="intervalInMilliseconds">The interval in milliseconds.</param>
        /// <param name="delayInMilliseconds">The delay in milliseconds, i.e. how long it waits to kick off the timer.</param>
        /// <param name="duration">The duration.
        /// <example>If the duration is set to 10 seconds, the maximum time this task is allowed to run is 10 seconds.</example></param>
        /// <param name="maxIterations">The max iterations.</param>
        /// <param name="synchronous">if set to <c>true</c> executes each period in a blocking fashion and each periodic execution of the task
        /// is included in the total duration of the Task.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <param name="periodicTaskCreationOptions"><see cref="TaskCreationOptions"/> used to create the task for executing the <see cref="Action"/>.</param>
        /// <returns>A <see cref="Task"/></returns>
        /// <remarks>
        /// Exceptions that occur in the <paramref name="action"/> need to be handled in the action itself. These exceptions will not be 
        /// bubbled up to the periodic task.
        /// </remarks>
        public static Task Start(Action action,
                                 int intervalInMilliseconds = Timeout.Infinite,
                                 int delayInMilliseconds = 0,
                                 int duration = Timeout.Infinite,
                                 int maxIterations = -1,
                                 bool synchronous = false,
                                 CancellationToken cancelToken = new CancellationToken(),
                                 TaskCreationOptions periodicTaskCreationOptions = TaskCreationOptions.None)
        {
            Stopwatch stopWatch = new Stopwatch();
            Action wrapperAction = () =>
            {
                CheckIfCancelled(cancelToken);
                action();
            };

            Action mainAction = () =>
            {
                MainPeriodicTaskAction(intervalInMilliseconds, delayInMilliseconds, duration, maxIterations, cancelToken, stopWatch, synchronous, wrapperAction, periodicTaskCreationOptions);
            };

            return Task.Factory.StartNew(mainAction, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
        }

        /// <summary>
        /// Mains the periodic task action.
        /// </summary>
        /// <param name="intervalInMilliseconds">The interval in milliseconds.</param>
        /// <param name="delayInMilliseconds">The delay in milliseconds.</param>
        /// <param name="duration">The duration.</param>
        /// <param name="maxIterations">The max iterations.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <param name="stopWatch">The stop watch.</param>
        /// <param name="synchronous">if set to <c>true</c> executes each period in a blocking fashion and each periodic execution of the task
        /// is included in the total duration of the Task.</param>
        /// <param name="wrapperAction">The wrapper action.</param>
        /// <param name="periodicTaskCreationOptions"><see cref="TaskCreationOptions"/> used to create a sub task for executing the <see cref="Action"/>.</param>
        private static void MainPeriodicTaskAction(int intervalInMilliseconds,
                                                   int delayInMilliseconds,
                                                   int duration,
                                                   int maxIterations,
                                                   CancellationToken cancelToken,
                                                   Stopwatch stopWatch,
                                                   bool synchronous,
                                                   Action wrapperAction,
                                                   TaskCreationOptions periodicTaskCreationOptions)
        {
            TaskCreationOptions subTaskCreationOptions = TaskCreationOptions.AttachedToParent | periodicTaskCreationOptions;

            CheckIfCancelled(cancelToken);

            if (delayInMilliseconds > 0)
            {
                Thread.Sleep(delayInMilliseconds);
            }

            if (maxIterations == 0) { return; }

            int iteration = 0;

            ////////////////////////////////////////////////////////////////////////////
            // using a ManualResetEventSlim as it is more efficient in small intervals.
            // In the case where longer intervals are used, it will automatically use 
            // a standard WaitHandle....
            // see http://msdn.microsoft.com/en-us/library/vstudio/5hbefs30(v=vs.100).aspx
            using (ManualResetEventSlim periodResetEvent = new ManualResetEventSlim(false))
            {
                ////////////////////////////////////////////////////////////
                // Main periodic logic. Basically loop through this block
                // executing the action
                while (true)
                {
                    CheckIfCancelled(cancelToken);

                    Task subTask = Task.Factory.StartNew(wrapperAction, cancelToken, subTaskCreationOptions, TaskScheduler.Current);

                    if (synchronous)
                    {
                        stopWatch.Start();
                        try
                        {
                            subTask.Wait(cancelToken);
                        }
                        catch { /* do not let an errant subtask to kill the periodic task...*/ }
                        stopWatch.Stop();
                    }

                    // use the same Timeout setting as the System.Threading.Timer, infinite timeout will execute only one iteration.
                    if (intervalInMilliseconds == Timeout.Infinite) { break; }

                    iteration++;

                    if (maxIterations > 0 && iteration >= maxIterations) { break; }

                    try
                    {
                        stopWatch.Start();
                        periodResetEvent.Wait(intervalInMilliseconds, cancelToken);
                        stopWatch.Stop();
                    }
                    finally
                    {
                        periodResetEvent.Reset();
                    }

                    CheckIfCancelled(cancelToken);

                    if (duration > 0 && stopWatch.ElapsedMilliseconds >= duration) { break; }
                }
            }
        }

        /// <summary>
        /// Checks if cancelled.
        /// </summary>
        /// <param name="cancelToken">The cancel token.</param>
        private static void CheckIfCancelled(CancellationToken cancellationToken)
        {
            if (cancellationToken == null)
                throw new ArgumentNullException("cancellationToken");

            cancellationToken.ThrowIfCancellationRequested();
        }
    }
}

Output:

2/18/2013 4:17:13 PM
2/18/2013 4:17:15 PM
2/18/2013 4:17:17 PM
2/18/2013 4:17:19 PM
2/18/2013 4:17:21 PM
2/18/2013 4:17:23 PM
2/18/2013 4:17:25 PM
2/18/2013 4:17:27 PM
2/18/2013 4:17:29 PM
2/18/2013 4:17:31 PM
Finished!
Press any key to continue . . .
Jim
  • 4,910
  • 4
  • 32
  • 50
  • 2
    This looks like great code, but I'm wondering if it's necessary now that there are the async/await keywords. How does your approach compare to the one here: http://stackoverflow.com/a/14297203/122781 ? – HappyNomad Mar 01 '13 at 23:08
  • 1
    @HappyNomad, looks like the PeriodicTaskFactory class could take advantage async/await for applications targeting .Net 4.5 but for us, we cannot move to .Net 4.5 yet. Also, the PeriodicTaskFactory provides some additional "timer" termination mechanisms such as max number of iterations and max duration as well as providing a way to ensure each iteration can wait on the last iteration. But I will be looking to adapt this to use async/await when we move to .Net 4.5 – Jim Mar 04 '13 at 15:13
  • 4
    +1 I'm using your class now, thanks. To get it to play nice with the UI thread, though, I do have to call `TaskScheduler.FromCurrentSynchronizationContext()` before setting `mainAction`. I then pass the resulting scheduler into `MainPeriodicTaskAction` for it to create the `subTask` with. – HappyNomad Mar 10 '13 at 02:19
  • @Jim I'm using your code and it worked perfectly fine until I came to point I need running few simultaneous periodic tasks. What happens if I schedule 10+ of such operations, only first 8 are executed in time, and each next one is delayed for 1 second (?). Yet didn't find what is causing this. Here's test project, maybe you can look - https://dl.dropboxusercontent.com/u/3816832/CA-TasksTests.zip. – Jaded Sep 22 '13 at 20:18
  • 1
    @Jaded-It could be the underlying TaskScheduler is running out of worker threads and waits until one is free. I tried this with a variation on the StaTaskScheduler from http://blogs.msdn.com/b/pfxteam/archive/2010/04/04/9990342.aspx, setting the concurrency to 10 (threads). Each periodic task then started at the same time. I used this task scheduler in your code on line 84 like so: }, CancellationToken.None, TaskCreationOptions.None, new ThreadLimitingTaskScheduler(10)); – Jim Sep 25 '13 at 17:00
  • I noticed a bit of skew when this is run, that I'm not sure how to correct. What are your thoughts about using this approach to inspire a highresolution periodic timer? http://stackoverflow.com/a/14369695/328397 – makerofthings7 Aug 05 '14 at 15:54
  • @makerofthings, Because of the nature of Task scheduling, you're not going to get exact high resolution execution of each iteration. The link you provided might be a basis though or perhaps use high resolution media timers to fire your events. – Jim Aug 05 '14 at 17:24
  • 2
    I don't sure, this is a good idea to block a thread, when it can do usefull work. "Thread.Sleep(delayInMilliseconds)", "periodResetEvent.Wait(intervalInMilliseconds, cancelToken)"... Then you use a Timer, you wait in hardware, so no threads a spent. But in your solution, threads are spent for nothing. – RollingStone Mar 02 '17 at 09:36
  • 2
    @rollingstone I agree. I think this solution largely defeats the purpose of async-like behavior. Much better to use a timer and not waste the thread. This is just giving the appearance of async without any of the benefits. – Jeff Mar 09 '17 at 23:29
13

It's not exactly in System.Threading.Tasks, but Observable.Timer (or simpler Observable.Interval) from Reactive Extensions library is probably what you're looking for.

mstone
  • 414
  • 4
  • 11
10

Until now I used a LongRunning TPL task for cyclic CPU bound background work instead of the threading timer, because:

  • the TPL task supports cancellation
  • the threading timer could start another thread while the programm is shutting down causing possible problems with disposed resources
  • chance for overrun: the threading timer could start another thread while the previous is still being processed due to unexpected long work (I know, it can be prevented by stopping and restarting the timer)

However, the TPL solution always claims a dedicated thread which is not necessary while waiting for the next action (which is most of the time). I would like to use the proposed solution of Jeff to perform CPU bound cyclic work on the background because it only needs a threadpool thread when there is work to do which is better for scalability (especially when the interval period is big).

To achieve that, I would suggest 4 adaptions:

  1. Add ConfigureAwait(false) to the Task.Delay() to execute the doWork action on a thread pool thread, otherwise doWork will be performed on the calling thread which is not the idea of parallelism
  2. Stick to the cancellation pattern by throwing a TaskCanceledException (still required ?)
  3. Forward the CancellationToken to doWork to enable it to cancel the task
  4. Add a parameter of type object to supply task state information (like a TPL task)

About point 2 I'm not sure, does async await still requires the TaskCanceledExecption or is it just best practice?

    public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
    {
        do
        {
            await Task.Delay(period, cancellationToken).ConfigureAwait(false);
            cancellationToken.ThrowIfCancellationRequested();
            doWork(taskState, cancellationToken);
        }
        while (true);
    }

Please give your comments to the proposed solution...

Update 2016-8-30

The above solution doesn't immediately call doWork() but starts with await Task.Delay().ConfigureAwait(false) to achieve the thread switch for doWork(). The solution below overcomes this problem by wrapping the first doWork() call in a Task.Run() and await it.

Below is the improved async\await replacement for Threading.Timer that performs cancelable cyclic work and is scalable (compared to the TPL solution) because it doesn’t occupy any thread while waiting for the next action.

Note that in contrary with the Timer, the waiting time (period) is constant and not the cycle time; the cycle time is the sum of the waiting time and the duration of doWork() which can vary.

    public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
    {
        await Task.Run(() => doWork(taskState, cancellationToken), cancellationToken).ConfigureAwait(false);
        do
        {
            await Task.Delay(period, cancellationToken).ConfigureAwait(false);
            cancellationToken.ThrowIfCancellationRequested();
            doWork(taskState, cancellationToken);
        }
        while (true);
    }
Erik Stroeken
  • 519
  • 4
  • 16
  • Using `ConfigureAwait(false)` will schedule the continuation of the method to the thread pool, so it doesn't really solve the second point re the threading timer. I also don't think `taskState` is necessary; lambda variable capture is more flexible and type-safe. – Stephen Cleary Aug 25 '16 at 20:01
  • 1
    What I really want to do is to exchange `await Task.Delay()` and `doWork()` so `doWork()` would immediately execute during startup. But without some trick `doWork()` would execute on the calling thread the first time and block it. Stephen, do you have a solution for that problem? – Erik Stroeken Aug 29 '16 at 06:20
  • 1
    The easiest way is to just wrap the whole thing in a `Task.Run`. – Stephen Cleary Aug 29 '16 at 15:09
  • Yes, but then I can just go back to the TPL solution I use now which claims a thread as long as the loop is running and thus is less scalable then this solution. – Erik Stroeken Aug 30 '16 at 08:47
6

I needed to trigger the recurring asynchronous tasks from a synchronous method.

public static class PeriodicTask
{
    public static async Task Run(
        Func<Task> action,
        TimeSpan period,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        while (!cancellationToken.IsCancellationRequested)
        {

            Stopwatch stopwatch = Stopwatch.StartNew();

            if (!cancellationToken.IsCancellationRequested)
                await action();

            stopwatch.Stop();

            await Task.Delay(period - stopwatch.Elapsed, cancellationToken);
        }
    }
}

This is an adaption of Jeff's answer. It is changed to take in a Func<Task> It also makes sure that the period is how often it is run by deducting the task's run time from the period for the next delay.

class Program
{
    static void Main(string[] args)
    {
        PeriodicTask
            .Run(GetSomething, TimeSpan.FromSeconds(3))
            .GetAwaiter()
            .GetResult();
    }

    static async Task GetSomething()
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        Console.WriteLine($"Hi {DateTime.UtcNow}");
    }
}
chris31389
  • 8,414
  • 7
  • 55
  • 66
2

I ran into a similar problem and wrote a TaskTimer class that returns a seris of tasks that complete on timer: https://github.com/ikriv/tasktimer/.

using (var timer = new TaskTimer(1000).Start())
{
    // Call DoStuff() every second
    foreach (var task in timer)
    {
        await task;
        DoStuff();
    }
}
Ivan Krivyakov
  • 1,898
  • 1
  • 17
  • 27
-1
static class Helper
{
    public async static Task ExecuteInterval(Action execute, int millisecond, IWorker worker)
    {
        while (worker.Worked)
        {
            execute();

            await Task.Delay(millisecond);
        }
    }
}


interface IWorker
{
    bool Worked { get; }
}

Simple...

nim
  • 357
  • 1
  • 5
  • 16