【问题标题】:How to use the .NET Timer class to trigger an event at a specific time?如何使用 .NET Timer 类在特定时间触发事件?
【发布时间】:2010-12-25 01:51:36
【问题描述】:

我想在我的应用中触发一个事件,该事件在白天的某个时间(例如下午 4:00)连续运行。我想过每秒运行一次计时器,当时间等于下午 4:00 时运行该事件。这样可行。但我想知道是否有办法只在下午 4:00 获得一次回调,而不必继续检查。

【问题讨论】:

    标签: c# .net timer


    【解决方案1】:

    这样的事情怎么样,使用System.Threading.Timer 类?

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

    请注意,如果您使用 System.Threading.Timer,则由 TimerCallback 指定的回调将在线程池(非 UI)线程上执行 - 因此,如果您计划在 4:00 使用您的 UI 执行某些操作,您必须适当地编组代码(例如,在 Windows 窗体应用程序中使用 Control.Invoke,或在 WPF 应用程序中使用 Dispatcher.Invoke)。

    【讨论】:

    • 听起来不错,但有一个问题。 , 但是 t.Change(timeUntilFour, Timeout.Infinite); 给出一个错误,指出时间跨度、时间跨度没有过载。我在文档中看到我也可以调用 dispose 但不确定在哪里。有什么想法吗?
    • 这行得通:t.Change(timeUntilFour, new TimeSpan(Timeout.Infinite));
    • 在休眠、待机、...的情况下是否能正常工作?
    • 此代码假定从午夜到下午 4:00 有 16 小时。该假设在夏令时转换期间无效。
    • 该代码还假定系统时钟始终准确。如果有错误需要调整,代码不会检测到并相应地调整其在t 上的剩余时间。
    【解决方案2】:

    从 .NET 4.5 开始,有一个非常干净的解决方案:

    public async void ScheduleAction(Action action, DateTime ExecutionTime)
    {
        await Task.Delay((int)ExecutionTime.Subtract(DateTime.Now).TotalMilliseconds);
        action();
    }
    

    这是一个没有 async/await 的解决方案:

    public void Execute(Action action, DateTime ExecutionTime)
    {
        Task WaitTask = Task.Delay((int)ExecutionTime.Subtract(DateTime.Now).TotalMilliseconds);
        WaitTask.ContinueWith(_ => action);
        WaitTask.Start();
    }
    

    应该注意,由于 int32 最大值,这仅适用于大约 24 天,这对于您的情况来说已经足够了,但值得注意。

    【讨论】:

    • 您现在实际上可以为 Task.Delay 使用 TimeSpan,这意味着您可以超过 VoteCoffee 提到的 24 天限制。 TimeSpan 基本上没有限制,比如 1000 万天,msdn.microsoft.com/en-us/library/…
    • 不错的解决方案,但“ExecutionTime”必须在未来,否则 Task.Delay 将抛出 ArgumentOutOfRangeException。
    • 小心使用async voidaction()中的异常会导致整个应用程序崩溃。阅读更多:haacked.com/archive/2014/11/11/async-void-methods
    • @chris84948 即使你给它一个时间跨度,Task.Delay(Date.Subtract(DateTime.Now)); 它会在超过 int32.MaxValue 时抛出 AurgumentOutOfRange。
    • 这对我来说似乎有点危险。我认为您应该检查计算的毫秒数是否为正。特别是因为Task.Delay(-1) 永远不会完成,并且会抛出其他负值。
    【解决方案3】:

    您可以使用Task Sceduler on windows 了解详情,请参阅daily trigger example

    如果你想自己写,也可以使用下面的代码:

    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
    }
    

    【讨论】:

    • @Øyvind Bråthen,我每小时(之前)写了这个,你可以在 cmets 中看到它,你可以使用 DateTime.Today.AddHours(16) 为 16,但你也可以使用它如果DateTime.Now 在您的时间间隔内是 16:00,它将每小时调用一次,另外我的主要关注点是使用 Windows 任务调度程序而不是创建任务。 (我不喜欢编辑代码,我把它留作作业:D,Dan代码类似)
    【解决方案4】:

    以 VoteCoffees 为主导,这是一个基于事件的紧凑型解决方案:

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

    消费者:`

    void Main()
    {
        var trigger = new DailyTrigger(16); // every day at 4:00pm
    
        trigger.OnTimeTriggered += () => 
        {
            // Whatever
        };  
        
        Console.ReadKey();
    }
    

    【讨论】:

      【解决方案5】:

      .NET 有很多计时器类,但它们都占用相对于当前时间的时间跨度。对于相对时间,在启动计时器之前需要考虑很多事情,并在计时器运行时进行监控。

      • 如果计算机进入待机状态怎么办?
      • 如果计算机的时间发生变化怎么办?
      • 如果到期时间在夏令时转换之后怎么办?
      • 如果用户在您启动计时器后更改计算机的时区,从而在到期前出现以前不存在的新时区转换,该怎么办?

      操作系统非常适合处理这种复杂性。 .NET 上运行的应用程序代码不是。

      对于 Windows,NuGet 包 AbsoluteTimer 包装了一个在绝对时间到期的操作系统计时器。

      【讨论】:

        【解决方案6】:

        我这样做是为了每天早上 7 点开火

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

        【讨论】:

        • 从某种意义上说,这是让某些东西在特定时间每天运行的最简单方法。我在一些 IMO 服务中使用这种风格进行日常家务。
        【解决方案7】:

        任务调度器是更好的选择,在C#中可以轻松使用,http://taskscheduler.codeplex.com/

        【讨论】:

          【解决方案8】:

          与 Dan 的解决方案相呼应,使用 Timercallback 是一种快速而简洁的解决方案。 在要安排要运行的任务或子例程的方法中,使用以下命令:

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

          使用“Timeout.Infinite”将确保回调仅在 400 毫秒后执行一次。我正在使用 VB.Net。

          【讨论】:

            【解决方案9】:

            这个解决方案怎么样?

            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
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-01-28
              • 1970-01-01
              • 1970-01-01
              • 2011-06-14
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多