How to use the .NET Timer class to trigger an event at a specific time?
I would like to have an event triggered in my app which runs continuously during the day at a certain time, say at 4:00pm. I thought about running the timer every second and when the time is equal to 4:00pm run the event. That works. But I'm wondering if there's a way to just get the callback once at 4:00pm and not having to keep checking.
Solution 1:
How about something like this, using the System.Threading.Timer
class?
var t = new Timer(TimerCallback);
// Figure how much time until 4:00
DateTime now = DateTime.Now;
DateTime fourOClock = DateTime.Today.AddHours(16.0);
// If it's already past 4:00, wait until 4:00 tomorrow
if (now > fourOClock)
{
fourOClock = fourOClock.AddDays(1.0);
}
int msUntilFour = (int)((fourOClock - now).TotalMilliseconds);
// Set the timer to elapse only once, at 4:00.
t.Change(msUntilFour, Timeout.Infinite);
Note that if you use a System.Threading.Timer
, the callback specified by TimerCallback
will be executed on a thread pool (non-UI) thread—so if you're planning on doing something with your UI at 4:00, you'll have to marshal the code appropriately (e.g., using Control.Invoke
in a Windows Forms app, or Dispatcher.Invoke
in a WPF app).
Solution 2:
Starting with .NET 4.5 there's a really clean solution:
public async void ScheduleAction(Action action, DateTime ExecutionTime)
{
await Task.Delay((int)ExecutionTime.Subtract(DateTime.Now).TotalMilliseconds);
action();
}
Here's a solution without async/await:
public void Execute(Action action, DateTime ExecutionTime)
{
Task WaitTask = Task.Delay((int)ExecutionTime.Subtract(DateTime.Now).TotalMilliseconds);
WaitTask.ContinueWith(_ => action);
WaitTask.Start();
}
It should be noted that this only works for about 24 days out because of int32 max value, which is plenty for your case, but worth noting.
Solution 3:
You can use Task Sceduler on windows See daily trigger example for detail.
or use bellow code if you want wrote it yourself:
public void InitTimer()
{
DateTime time = DateTime.Now;
int second = time.Second;
int minute = time.Minute;
if (second != 0)
{
minute = minute > 0 ? minute-- : 59;
}
if (minute == 0 && second == 0)
{
// DoAction: in this function also set your timer interval to 24 hours
}
else
{
TimeSpan span = //new daily timespan, previous code was hourly: new TimeSpan(0, 60 - minute, 60 - second);
timer.Interval = (int) span.TotalMilliseconds - 100;
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
}
void timer_Tick(object sender, EventArgs e)
{
timer.Interval = ...; // 24 hours
// DoAction
}
Solution 4:
Taking VoteCoffees lead, here is a compact event based solution:
/// <summary>
/// Utility class for triggering an event every 24 hours at a specified time of day
/// </summary>
public class DailyTrigger : IDisposable
{
/// <summary>
/// Time of day (from 00:00:00) to trigger
/// </summary>
TimeSpan TriggerHour { get; }
/// <summary>
/// Task cancellation token source to cancel delayed task on disposal
/// </summary>
CancellationTokenSource CancellationToken { get; set; }
/// <summary>
/// Reference to the running task
/// </summary>
Task RunningTask { get; set; }
/// <summary>
/// Initiator
/// </summary>
/// <param name="hour">The hour of the day to trigger</param>
/// <param name="minute">The minute to trigger</param>
/// <param name="second">The second to trigger</param>
public DailyTrigger(int hour, int minute = 0, int second = 0)
{
TriggerHour = new TimeSpan(hour, minute, second);
CancellationToken = new CancellationTokenSource();
RunningTask = Task.Run(async () =>
{
while (true)
{
var triggerTime = DateTime.Today + TriggerHour - DateTime.Now;
if (triggerTime < TimeSpan.Zero)
triggerTime = triggerTime.Add(new TimeSpan(24, 0, 0));
await Task.Delay(triggerTime, CancellationToken.Token);
OnTimeTriggered?.Invoke();
}
}, CancellationToken.Token);
}
/// <inheritdoc/>
public void Dispose()
{
CancellationToken?.Cancel();
CancellationToken?.Dispose();
CancellationToken = null;
RunningTask?.Dispose();
RunningTask = null;
}
/// <summary>
/// Triggers once every 24 hours on the specified time
/// </summary>
public event Action OnTimeTriggered;
/// <summary>
/// Finalized to ensure Dispose is called when out of scope
/// </summary>
~DailyTrigger() => Dispose();
}
Consumer:`
void Main()
{
var trigger = new DailyTrigger(16); // every day at 4:00pm
trigger.OnTimeTriggered += () =>
{
// Whatever
};
Console.ReadKey();
}