【问题标题】:Alarm clock application in .Net.Net 中的闹钟应用程序
【发布时间】:2010-12-02 08:49:03
【问题描述】:

我并不是真的在编写闹钟应用程序,但它有助于说明我的问题。

假设我的应用程序中有一个方法,并且我希望每小时整点调用此方法(例如,在晚上 7:00、晚上 8:00、晚上 9:00 等)。我可以创建一个计时器并将其间隔设置为 3600000,但最终这会与系统时钟不同步。或者我可以使用while() 循环和Thread.Sleep(n) 定期检查系统时间并在达到所需时间时调用该方法,但我也不喜欢这样(Thread.Sleep(n) 是对我来说有很大的代码味道)。

我正在寻找的是 .Net 中的一些方法,它可以让我传入未来的 DateTime 对象和方法委托或事件处理程序,但我找不到任何这样的东西。我怀疑 Win32 API 中有一个方法可以做到这一点,但我也找不到。

【问题讨论】:

  • 您是否希望推出自己的 Quartz.Net? quartznet.sourceforge.net
  • @Austin:不,我只是在寻找一种简单的方法,就像我在上一段中描述的那样,而不必自己动手或使用一些第三方组件。
  • 内置的 Windows 调度程序有一个可通过 PInvoke pinvoke.net/default.aspx/netapi32/NetScheduleJobAdd.html 访问的 API
  • @xcud:NetScheduleJobAdd 与我要查找的内容很接近,但它执行的是命令字符串而不是调用方法。

标签: c# .net datetime time


【解决方案1】:

或者,您可以创建一个间隔为 1 秒的计时器,并每隔一秒检查一次当前时间,直到达到事件时间,如果是,您就发起您的事件。

您可以为此制作一个简单的包装器:

public class AlarmClock
{
    public AlarmClock(DateTime alarmTime)
    {
        this.alarmTime = alarmTime;

        timer = new Timer();
        timer.Elapsed += timer_Elapsed;
        timer.Interval = 1000;
        timer.Start();

        enabled = true;
    }

    void  timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if(enabled && DateTime.Now > alarmTime)
        {
            enabled = false;
            OnAlarm();
            timer.Stop();
        }
    }

    protected virtual void OnAlarm()
    {
        if(alarmEvent != null)
            alarmEvent(this, EventArgs.Empty);
    }


    public event EventHandler Alarm
    {
        add { alarmEvent += value; }
        remove { alarmEvent -= value; }
    }

    private EventHandler alarmEvent;
    private Timer timer;
    private DateTime alarmTime;
    private bool enabled;
}

用法:

AlarmClock clock = new AlarmClock(someFutureTime);
clock.Alarm += (sender, e) => MessageBox.Show("Wake up!");

请注意,上面的代码非常粗略,不是线程安全的。

【讨论】:

  • 是的,但这也不是我想要的,抱歉。
  • 您要查找的内容不存在。
  • 抱歉,那你在找什么?
  • 我认为MusiGenesis 真的想要一个作业调度器。
  • 你可以计算间隔而不是每秒检查一次。
【解决方案2】:

有趣的是,我实际上遇到了一个非常相似的问题,并在 .Net 框架中寻找一种可以处理这种情况的方法。最后,我们最终实现了我们自己的解决方案,它是一个带有 Thread.Sleep(n) 的 while 循环的变体,其中 n 越接近所需的目标时间越小(实际上是对数,但有一些合理的阈值,所以当你接近目标时间时,你并没有最大化 CPU。)这是一个非常简单的实现,它只是从现在到目标时间的一半时间休眠。

class Program
{
    static void Main(string[] args)
    {
        SleepToTarget Temp = new SleepToTarget(DateTime.Now.AddSeconds(30),Done);
        Temp.Start();
        Console.ReadLine();
    }

    static void Done()
    {
        Console.WriteLine("Done");
    }
}

class SleepToTarget
{
    private DateTime TargetTime;
    private Action MyAction;
    private const int MinSleepMilliseconds = 250;

    public SleepToTarget(DateTime TargetTime,Action MyAction)
    {
        this.TargetTime = TargetTime;
        this.MyAction = MyAction;
    }

    public void Start()
    {
        new Thread(new ThreadStart(ProcessTimer)).Start();
    }

    private void ProcessTimer()
    {
        DateTime Now = DateTime.Now;

        while (Now < TargetTime)
        {
            int SleepMilliseconds = (int) Math.Round((TargetTime - Now).TotalMilliseconds / 2);
            Console.WriteLine(SleepMilliseconds);
            Thread.Sleep(SleepMilliseconds > MinSleepMilliseconds ? SleepMilliseconds : MinSleepMilliseconds);
            Now = DateTime.Now;
        }

        MyAction();
    }
}

【讨论】:

    【解决方案3】:

    您可以简单地在每次触发时重置计时器持续时间,如下所示:

    // using System.Timers;
    
    private void myMethod()
    {
        var timer = new Timer { 
            AutoReset = false, Interval = getMillisecondsToNextAlarm() };
        timer.Elapsed += (src, args) =>
        {
            // Do timer handling here.
    
            timer.Interval = getMillisecondsToNextAlarm();
            timer.Start();
        };
        timer.Start();
    }
    
    private double getMillisecondsToNextAlarm()
    {
        // This is an example of making the alarm go off at every "o'clock"
        var now = DateTime.Now;
        var inOneHour = now.AddHours(1.0);
        var roundedNextHour = new DateTime(
            inOneHour.Year, inOneHour.Month, inOneHour.Day, inOneHour.Hour, 0, 0);
        return (roundedNextHour - now).TotalMilliseconds;
    }
    

    【讨论】:

    • 不幸的是,这并不能解决问题。漂移问题是因为计时器长时间不准确(秒表也有同样的问题)。
    • 我以前从未听说过。您提出这种不准确的说法的依据是什么?
    • @Jacob:不要相信我的话——你自己试试吧。启动一个计时器(任何种类),让它运行一两天,看看它有多远。
    • 我不明白这个“漂移”问题。我在服务中使用了一个经过时间为 5 秒的 System.Timers.Timer,并且在我使用它的四个月内没有任何偏差。我知道这是一个事实,因为服务在每个时间间隔都会写入事件日志,并且我每五秒就有一次事件,就像发条一样,这是预期的。
    • @AMissico:您的服务是否只启动了一次 Timer,然后在每次 Elapsed 调用时写入事件日志,还是 Windows 定期重新启动您的服务?我刚刚再次测试,我的计时器(间隔为 5000)仅在 12 分钟后就从系统时间漂移了整整一秒。
    【解决方案4】:

    您可以创建一个具有专用线程的 Alarm 类,该线程会在指定时间之前进入睡眠状态,但这将使用 Thread.Sleep 方法。比如:

    /// <summary>
    /// Alarm Class
    /// </summary>
    public class Alarm
    {
        private TimeSpan wakeupTime;
    
        public Alarm(TimeSpan WakeUpTime)
        {
            this.wakeupTime = WakeUpTime;
            System.Threading.Thread t = new System.Threading.Thread(TimerThread) { IsBackground = true, Name = "Alarm" };
            t.Start();
        }
    
        /// <summary>
        /// Alarm Event
        /// </summary>
        public event EventHandler AlarmEvent = delegate { };
    
        private void TimerThread()
        {
            DateTime nextWakeUp = DateTime.Today + wakeupTime;
            if (nextWakeUp < DateTime.Now) nextWakeUp = nextWakeUp.AddDays(1.0);
    
            while (true)
            {
                TimeSpan ts = nextWakeUp.Subtract(DateTime.Now);
    
                System.Threading.Thread.Sleep((int)ts.TotalMilliseconds);
    
                try { AlarmEvent(this, EventArgs.Empty); }
                catch { }
    
                nextWakeUp = nextWakeUp.AddDays(1.0);
            }
        }
    }
    

    【讨论】:

    • 确实如此。更多你可以做什么的原型。最好使用 Jacob 描述的 Timer。
    【解决方案5】:

    我知道这是一个老问题,但我在寻找其他问题的答案时遇到了这个问题。因为我最近遇到了这个特殊问题,所以我想我会在这里投入两分钱。

    您可以做的另一件事是像这样安排方法:

    /// Schedule the given action for the given time.
    public async void ScheduleAction ( Action action , DateTime ExecutionTime )
    {
        try
        {
            await Task.Delay ( ( int ) ExecutionTime.Subtract ( DateTime.Now ).TotalMilliseconds );
            action ( );
        }
        catch ( Exception )
        {
            // Something went wrong
        }
    }
    
    

    请记住,它最多只能等待 int 32 的最大值(大约一个月左右),它应该可以满足您的目的。用法:

    void MethodToRun ( )
    {
        Console.WriteLine ("Hello, World!");
    }
    
    void CallingMethod ( )
    {
        var NextRunTime = DateTime.Now.AddHours(1);
        ScheduleAction ( MethodToRun, NextRunTime );
    }
    

    你应该在一小时内收到一条控制台消息。

    【讨论】:

      【解决方案6】:

      【讨论】:

      • 不,这与我正在寻找的东西完全不同。
      【解决方案7】:

      我以前用过这个很成功:

      vb.net:

      Imports System.Threading
      Public Class AlarmClock
          Public startTime As Integer = TimeOfDay.Hour
          Public interval As Integer = 1
          Public Event SoundAlarm()
          Public Sub CheckTime()
              While TimeOfDay.Hour < startTime + interval
                  Application.DoEvents()
              End While
              RaiseEvent SoundAlarm()
          End Sub
          Public Sub StartClock()
              Dim clockthread As Thread = New Thread(AddressOf CheckTime)
              clockthread.Start()
          End Sub
      End Class
      

      C#:

      using System.Threading;
      public class AlarmClock
      {
          public int startTime = TimeOfDay.Hour;
          public int interval = 1;
          public event SoundAlarmEventHandler SoundAlarm;
          public delegate void SoundAlarmEventHandler();
          public void CheckTime()
          {
              while (TimeOfDay.Hour < startTime + interval) {
                  Application.DoEvents();
              }
              if (SoundAlarm != null) {
                  SoundAlarm();
              }
          }
          public void StartClock()
          {
              Thread clockthread = new Thread(CheckTime);
              clockthread.Start();
          }
      }
      

      我不知道c#能不能用,但是vb能用就好了。

      在VB中的使用:

      Dim clock As New AlarmClock
      clock.interval = 1 'Interval is in hours, could easily convert to anything else
      clock.StartClock()
      

      然后,只需为 SoundAlarm 事件添加一个事件处理程序。

      【讨论】:

      • TimeOfDay 是 VB.Net 独有的,而带有 DoEvents() 调用的 While 循环将在这个东西运行时最大化你的处理器。
      • 不知道,但现在我知道了。原始代码让循环休眠了 15 秒,但既然你不想这样,我就把它省略了。
      • 为什么你会希望 Application.DoEvents 在一个线程中运行(甚至根本没有)的方法中
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-15
      • 2012-03-15
      相关资源
      最近更新 更多