开发者

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开发者_JAVA技巧:00pm and not having to keep checking.


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).


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.


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();
}


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
}


.NET has lots of timer classes, but they all take time spans relative to the current time. With a relative time, there are lots of things to consider before you start your timer and to monitor for while your timer is running.

  • What if the computer goes into a standby state?
  • What if the computer's time changes?
  • What if the expiration time is after a daylight savings time transition?
  • What if the user changes the computer's time zone after you started your timer such that there is a new time zone transition before expiration that wasn't previously there?

The operating system is in a great place to handle this complexity. Application code running on .NET is not.

For Windows, the NuGet package AbsoluteTimer wraps an operating system timer that expires at an absolute time.


I did this way to fire 7am every morning

bool _ran = false; //initial setting at start up
    private void timer_Tick(object sender, EventArgs e)
    {

        if (DateTime.Now.Hour == 7 && _ran==false)
        {
            _ran = true;
            Do_Something();               

        }

        if(DateTime.Now.Hour != 7 && _ran == true)
        {
            _ran = false;
        }

    }


Task scheduler is a better option, and it can be easily used in C#, http://taskscheduler.codeplex.com/


Echo to Dan's solution, using Timercallback is a quick and neat solution. Inside the method you want to schedule a task or subroutine to be run, use the following:

    t = New Timer(Sub()
                        'method call or code here'
                  End Sub, Nothing, 400, Timeout.Infinite)

use of 'Timeout.Infinite' will ensure the callback will be executed only once after 400ms. I am using VB.Net.


How about this solution?

Sub Main()
  Dim t As New Thread(AddressOf myTask)
  t.Start()
  Console.ReadLine()
End Sub

Private Sub myTask()
  Dim a = "14:35"
  Dim format = "dd/MM/yyyy HH:mm:ss"
  Dim targetTime = DateTime.Parse(a)
  Dim currentTime = DateTime.Parse(Now.ToString(format))
  Console.WriteLine(currentTime)
  Console.WriteLine("target time " & targetTime)
  Dim bb As TimeSpan = targetTime - currentTime
  If bb.TotalMilliseconds < 0 Then
    targetTime = targetTime.AddDays(1)
    bb = targetTime - currentTime
  End If
  Console.WriteLine("Going to sleep at " & Now.ToString & " for " & bb.TotalMilliseconds)
  Thread.Sleep(bb.TotalMilliseconds)
  Console.WriteLine("Woke up at " & Now.ToString(format))
End Sub
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜