I want to develop a windows console application which executes an action periodically after a given time. I've read somewhere that a timer class is only available for windows forms applications, so what is the best way to accomplish what I want?
5 Answers
Original message: I wrote this sample console application (using C# 4.0 [as indicated by the default parameters]). It's extremely versatile and utilizes the Action delegate to allow passing snippets that you want executed using the Timer class (in System.Threading). The overloaded Do method of the At static class does all the legwork of computing the interval of delay. You can optionally have the snipped repeated by specifying an interval (in milliseconds). You can of course modify the method to accept TimeSpan for the repeat interval.
The console application example provided three years ago has proven pretty popular for folks looking for something of this nature. Many of the questions asked dealt with job schedule management. I took a crack at writing a new class that can be implemented fairly simply. It's provided only as an example of use. For those who believe my style is inferior or riddled with unnecessary code, that's fine by me. It's up to you to tweak it for your programming style, implementation practices, etc.
The following changes to the class make it more usable:
- Creating jobs with the Do() method works similarly as the old class. An additional optional parameter key (type object) has been provided to let you specify the name of a job. This name will let you modify previously scheduled jobs (keep reading).
- Instantiated Timer objects are kept internally within the class allowing you to have multiple scheduled tasks that can be referenced by name individually.
- New handlers have been added for job management (using the key). They are provided by the SuspendJob(), GetJobFor(), EndJob(), and ResumeJob methods.
- Static constructor subscribes to the AppDomain.CurrentDomain.DomainUnload event for the "deconstructor" to dispose any running jobs when the appdomain (application) is unloading.
- Parameter checking has been added to make sure parameters are what is expected.
- Scoped locking allows you to add jobs from any thread.
What hasn't changed?
- The example still doesn't handle the result of the Action delegate. If you need something better, implement it. :)
Scroll down to find the new implementation of the class. Enjoy!
** The old console class as follows: **
using System;
using System.Threading;
namespace ConsoleApplication1
{
/// <summary>
/// Class that manages the execution of tasks sometime in the future.
/// </summary>
public static class At
{
#region Members
/// <summary>
/// Specifies the method that will be fired to execute the delayed anonymous method.
/// </summary>
private readonly static TimerCallback timer = new TimerCallback(At.ExecuteDelayedAction);
#endregion
#region Methods
/// <summary>
/// Method that executes an anonymous method after a delay period.
/// </summary>
/// <param name="action">The anonymous method that needs to be executed.</param>
/// <param name="delay">The period of delay to wait before executing.</param>
/// <param name="interval">The period (in milliseconds) to delay before executing the anonymous method again (Timeout.Infinite to disable).</param>
public static void Do(Action action, TimeSpan delay, int interval = Timeout.Infinite)
{
// create a new thread timer to execute the method after the delay
new Timer(timer, action, Convert.ToInt32(delay.TotalMilliseconds), interval);
return;
}
/// <summary>
/// Method that executes an anonymous method after a delay period.
/// </summary>
/// <param name="action">The anonymous method that needs to be executed.</param>
/// <param name="delay">The period of delay (in milliseconds) to wait before executing.</param>
/// <param name="interval">The period (in milliseconds) to delay before executing the anonymous method again (Timeout.Infinite to disable).</param>
public static void Do(Action action, int delay, int interval = Timeout.Infinite)
{
Do(action, TimeSpan.FromMilliseconds(delay), interval);
return;
}
/// <summary>
/// Method that executes an anonymous method after a delay period.
/// </summary>
/// <param name="action">The anonymous method that needs to be executed.</param>
/// <param name="dueTime">The due time when this method needs to be executed.</param>
/// <param name="interval">The period (in milliseconds) to delay before executing the anonymous method again (Timeout.Infinite to disable).</param>
public static void Do(Action action, DateTime dueTime, int interval = Timeout.Infinite)
{
if (dueTime < DateTime.Now)
{
throw new ArgumentOutOfRangeException("dueTime", "The specified due time has already elapsed.");
}
Do(action, dueTime - DateTime.Now, interval);
return;
}
/// <summary>
/// Method that executes a delayed action after a specific interval.
/// </summary>
/// <param name="o">The Action delegate that is to be executed.</param>
/// <remarks>This method is invoked on its own thread.</remarks>
private static void ExecuteDelayedAction(object o)
{
// invoke the anonymous method
(o as Action).Invoke();
return;
}
#endregion
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Time: {0} - started", DateTime.Now);
// demonstrate that order is irrelevant
At.Do(() => Console.WriteLine("Time: {0} - Hello World! (after 5s)", DateTime.Now), DateTime.Now.AddSeconds(5));
At.Do(() => Console.WriteLine("Time: {0} - Hello World! (after 3s)", DateTime.Now), DateTime.Now.AddSeconds(3));
At.Do(() => Console.WriteLine("Time: {0} - Hello World! (after 1s)", DateTime.Now), DateTime.Now.AddSeconds(1));
At.Do
(
() =>
{
// demonstrate flexibility of anonymous methods
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Time: {0} - Hello World! - i == {1} (after 4s)", DateTime.Now, i);
}
},
TimeSpan.FromSeconds(4)
);
// block main thread to show execution of background threads
Thread.Sleep(100000);
return;
}
}
}
** The new class is provided as follows: **
using System;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
namespace Utility
{
/// <summary>
/// Class that is designed to execution Action-based anonymous delegates after a specified
/// interval. This class also supports repetitive tasks on an interval.
/// </summary>
public static class At
{
#region Embedded Classes
/// <summary>
/// Embedded class definition for common At job periods.
/// </summary>
public static class Periods
{
#region Members
/// <summary>
/// Specifies an object that indicates to not restart.
/// </summary>
public static readonly TimeSpan DoNotStart = TimeSpan.FromMilliseconds(-1.0);
/// <summary>
/// Specifies an object that indicates to start immediately.
/// </summary>
public static readonly TimeSpan Immediately = TimeSpan.FromMilliseconds(0.0);
/// <summary>
/// Specifies an interval of one second.
/// </summary>
public static readonly TimeSpan SecondsOne = TimeSpan.FromSeconds(1.0);
/// <summary>
/// Specifies an interval of five seconds.
/// </summary>
public static readonly TimeSpan SecondsFive = TimeSpan.FromSeconds(5.0);
/// <summary>
/// Specifies an interval of fifteen seconds.
/// </summary>
public static readonly TimeSpan SecondsFifteen = TimeSpan.FromSeconds(15.0);
/// <summary>
/// Specifies an interval of thirty seconds.
/// </summary>
public static readonly TimeSpan SecondsThirty = TimeSpan.FromSeconds(30.0);
/// <summary>
/// Specifies an interval of 100ms.
/// </summary>
public static readonly TimeSpan MicroDelay = TimeSpan.FromMilliseconds(100);
#endregion
}
#endregion
#region Members
/// <summary>
/// Specifies an object that can be used for synchronization.
/// </summary>
private readonly static object syncRoot;
/// <summary>
/// Specifies a collection of Timer object that were created for interval-based execution.
/// </summary>
/// <remarks>
/// We must keep these in a collection to prevent the GC from disposing of the timers.
/// </remarks>
private readonly static Dictionary<object, Timer> ActiveTimers;
/// <summary>
/// Specifies a collection of timestamps of when timers are created.
/// </summary>
private readonly static Dictionary<object, DateTime> TimerCreation;
/// <summary>
/// Specifies an object that will produce pseudo-random numbers.
/// </summary>
private readonly static Random RNG;
#endregion
#region Static Constructor
static At()
{
syncRoot = new object();
ActiveTimers = new Dictionary<object, Timer>();
TimerCreation = new Dictionary<object, DateTime>();
RNG = new Random();
// "deconstructor"
AppDomain.CurrentDomain.DomainUnload += new EventHandler(CurrentDomain_DomainUnload);
return;
}
/// <summary>
/// Method used to cleanup resources used by this object.
/// </summary>
static void CurrentDomain_DomainUnload(object sender, EventArgs e)
{
// dispose of all the timers directly
At.ActiveTimers.Values.ToList().ForEach(a => a.Dispose());
return;
}
#endregion
#region Methods
#region At Job Staging
/// <summary>
/// Method that executes an anonymous method after a delay period.
/// </summary>
/// <param name="action">The anonymous method that needs to be executed.</param>
/// <param name="delay">The period of delay to wait before executing.</param>
/// <param name="interval">The period (in milliseconds) to delay before executing the anonymous method again (Timeout.Infinite to disable).</param>
public static Timer Do(Action action, TimeSpan delay, TimeSpan? onInterval = null, object key = null)
{
Timer timer;
if (key == null)
{
// auto-generate a key
key = string.Concat("Auto(", At.RNG.NextNonNegativeLong(), ")");
}
lock (At.ActiveTimers)
{
// action - contains the method that we wish to invoke
At.ActiveTimers.Add(key, timer = new Timer(ActionInvoker, action, delay, onInterval ?? At.Periods.DoNotStart));
At.TimerCreation.Add(key, DateTime.Now);
}
//Log.Message
//(
// LogMessageType.Debug,
// "[DEBUG] {0}: registered At job (key = {1}, initial delay = {2}, interval = {3})",
// action,
// key,
// delay,
// (onInterval == null) ? "never" : onInterval.Value.ToString()
//);
return timer;
}
/// <summary>
/// Method that executes an anonymous method after a delay period.
/// </summary>
/// <param name="action">The anonymous method that needs to be executed.</param>
/// <param name="delay">The period of delay (in milliseconds) to wait before executing.</param>
/// <param name="interval">The period (in milliseconds) to delay before executing the anonymous method again (Timeout.Infinite to disable).</param>
public static Timer Do(Action action, int delay, int interval = Timeout.Infinite, object key = null)
{
return Do(action, TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(interval), key);
}
/// <summary>
/// Method that executes an anonymous method after a delay period.
/// </summary>
/// <param name="action">The anonymous method that needs to be executed.</param>
/// <param name="dueTime">The due time when this method needs to be executed.</param>
/// <param name="interval">The period (in milliseconds) to delay before executing the anonymous method again (Timeout.Infinite to disable).</param>
public static Timer Do(Action action, DateTime dueTime, int interval = Timeout.Infinite, object key = null)
{
if (dueTime < DateTime.Now)
{
throw new ArgumentOutOfRangeException("dueTime", "The specified due time has already elapsed.");
}
return Do(action, dueTime - DateTime.Now, TimeSpan.FromMilliseconds(interval), key);
}
#endregion
#region At Job Retrieval
/// <summary>
/// Method that attempts to retrieve a job (Timer object) for a given key.
/// </summary>
/// <param name="key">The key that we are getting a job for.</param>
public static Timer GetJobFor(object key)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
lock (At.ActiveTimers)
{
if (At.ActiveTimers.ContainsKey(key) == false)
{
/*
Log.Message
(
LogMessageType.Error,
"[ERROR] At({0}): unable to find a job with specified key",
key
);
*/
return null;
}
return At.ActiveTimers[key];
}
}
/// <summary>
/// Method that ends a job and removes all resources associated with it.
/// </summary>
/// <param name="key">The key that we are getting a job for.</param>
public static void EndJob(object key)
{
Timer timer;
if ((timer = GetJobFor(key)) == null)
{
// no timer - cannot suspend
return;
}
// dispose of the timer object
timer.Dispose();
lock (At.ActiveTimers)
{
// remove the existence from the dictionary
At.ActiveTimers.Remove(key);
/*
Log.Message
(
LogMessageType.Info,
"[INFO] At({0}): job has been disposed (created {1}, duration {2})",
key,
TimerCreation[key].ToISODateTime(),
(DateTime.Now - TimerCreation[key]).ToText()
);
*/
At.TimerCreation.Remove(key);
}
return;
}
/// <summary>
/// Method that attempts to suspend an active job (using the provided key).
/// </summary>
/// <param name="key">The key that we are getting a job for.</param>
public static void SuspendJob(object key)
{
Timer timer;
if ((timer = GetJobFor(key)) == null)
{
// no timer - cannot suspend
return;
}
// set the timer to not restart
timer.Change(TimeSpan.FromMilliseconds(-1), TimeSpan.FromMilliseconds(-1));
/*
Log.Message
(
LogMessageType.Info,
"[INFO] At({0}): job has been suspended",
key
);
*/
return;
}
/// <summary>
/// Method that attempts to resume an active job (using the provided key).
/// </summary>
/// <param name="key">The key that we are getting a job for.</param>
/// <param name="delay">The amount of delay before restarting the job (specify <b>0</b> to restart immediately).</param>
/// <param name="interval">The delay between intervals (specify <b>-1ms</b> to prevent intervals).</param>
public static void ResumeJob(object key, TimeSpan delay, TimeSpan interval)
{
Timer timer;
if ((timer = GetJobFor(key)) == null)
{
// no timer - cannot suspend
return;
}
// set the timer to not restart
timer.Change(delay, interval);
/*
Log.Message
(
LogMessageType.Info,
"[INFO] At({0}): job has been resumed (delay = {1}, interval = {2})",
key,
delay,
interval
);
*/
return;
}
#endregion
/// <summary>
/// Method that invokes an action delegate on a timer.
/// </summary>
/// <param name="o">A reference to the action that is to be taken.</param>
private static void ActionInvoker(object o)
{
// invoke the delegate
(o as Action).Invoke();
return;
}
#endregion
}
}

- 3,821
- 2
- 19
- 18
-
1hell! this should have more likes! – Arsen Zahray Oct 25 '12 at 17:31
-
1It will serve its purpose, but I wouldn't use it in production code in this form: first, you are not disposing your timers, meaning they are kept alive by their native timers, preventing GC from disposing and collecting them. Every time you call the `Do` method you are creating a new uncollectable instance. Other issues are more related to good programming practices: you don't have to end each method with an empty `return`, `ExecuteDelayedAction` should not take an `object` as a parameter to avoid casting, and `timer` is a bit poor naming choice for something which is not really a timer. – vgru Dec 05 '12 at 16:29
-
How could i dispose the timers? can you modify this code to solve that problem? – Luis Tellez Dec 26 '12 at 13:02
-
1The example was not meant to be production code. It's actually only about a third of what is contained in the entire class. – Michael Jan 18 '13 at 22:07
-
3Very nice concept. The issues raised by Groo are easily solved (or trivial). As for disposing the timers - I changed the class from static and implemented IDisposable. Instead of a "Do" method I allocated the timer in the constructor and dispose of it in the Dispose method. Thanks! – OSH Jan 31 '13 at 11:46
-
Is it possible cancel action before execute delayed action? – Kiquenet May 01 '13 at 06:36
-
In the version I presented here, there is no provided method. When creating a job, you'd need to keep a tab on the Timer() object created. If you want to cancel a job, simply dispose of its Timer() object. – Michael May 15 '13 at 20:16
-
I added an update to the example above that includes methods for job schedule management. It's an evolution of the original example. It should provide a better template for you to accomplish your goals. – Michael May 15 '13 at 20:44
-
if it should not be in production code, then it should not be here too. – Sayed Muhammad Idrees Jun 22 '20 at 08:57
-
You added a wonderful comment to something nearly 8 years old for an example that developers could build off of, learn from, and adapt? One that was edited to add more robustness based on comments 7 years ago? Kudos to you, sir. – Michael Jun 23 '20 at 14:09
You can use System.Threading.Timer (or, alternatively, System.Timers.Timer, which is effectively the same as System.Threading.Timer) in a Console application. It's only the Windows Forms or WPF specific timers you want to avoid.

- 554,122
- 78
- 1,158
- 1,373
Using Rx you can do this:
var timer = Observable.Interval(TimeSpan.FromSeconds(1));
timer.Subscribe(l => Console.WriteLine(l));
Thread.Sleep(Timeout.Infinite);
Just another option.

- 10,550
- 3
- 42
- 62
//<Namespace>.Utilities.Extensions
public static class ActionExtensions
{
public static void RunAfter(this Action action, TimeSpan span)
{
var dispatcherTimer = new DispatcherTimer { Interval = span };
dispatcherTimer.Tick += (sender, args) =>
{
var timer = sender as DispatcherTimer;
if (timer != null)
{
timer.Stop();
}
action();
};
dispatcherTimer.Start();
}
}
//<Namespace>.Utilities
public static class CommonUtil
{
public static void Run(Action action, TimeSpan afterSpan)
{
action.RunAfter(afterSpan);
}
}
Usage:
CommonUtil.Run(() =>
{
// some actions
}, TimeSpan.FromMilliseconds(5000));

- 361
- 3
- 3
-
`DispatcherTimer` can be replaced with `System.Windows.Forms.Timer` and it works in WinForms. – Vojtěch Dohnal Sep 15 '15 at 09:07
The simplest way to do something periodically is use code like this
new Timer((Object stateInfo) => { Console.WriteLine("Your Job every 1 sec"); }, new AutoResetEvent(false), 0, 1000);
If you want to make a delay (execute action after X seconds), change 0 to your number of milliseconds.
If you need delay only, change 1000 to Timeout.Infinite .
Of course, you have to block thread:
Console.ReadKey();
or
autoEvent.WaitOne()
if you use AutoResetEvent explicitly

- 487
- 3
- 7