【问题标题】:How to execute code at a specific exact time (far from now) in C#?如何在 C# 中的特定时间(远非现在)执行代码?
【发布时间】:2014-03-12 14:49:22
【问题描述】:

我正在开发一个必须在特定准确时间执行方法的 Windows 服务。

当服务启动时,它会从 SQL Server 数据库中提取几个不同的 DateTime,然后应该为每个 DateTime 启动一个线程。每个线程应该等到确切的 DateTime 值,然后做一些工作,然后再等待更短的时间,然后再做一些最后的工作。

我不想使用 System.Timers.Timer 因为:

  • 该方法必须在指定的确切时间执行
  • 这个确切的时间可能超过 24 小时
  • 我需要将参数传递给 Elapsed 事件

我该如何实现?

谢谢。

编辑:我昨晚刚有了一个想法,如果我写一个动态轮询计时器呢?例如,如果到期日期时间超过 12 小时,它可以每小时轮询时间以重新同步。然后,如果到期日期时间超过 3 小时,它可以每 30 分钟轮询一次时间,依此类推......你怎么看?那会是一个糟糕的代码吗?

再次感谢您。

【问题讨论】:

  • 这个问题的唯一正确答案是“你不能”,因为由于操作系统的限制,你不能将任何事情安排到精确的毫秒。话虽如此,为什么项目符号 1 和 2 会阻止您使用 System.Timers.Timer

标签: c# multithreading datetime timer


【解决方案1】:

您不能使用System.Timers.Timer 将参数传递给tick 事件,但可以使用System.Threading.Timer。延迟时间“大于 24 小时”是没有问题的。计时器使用以毫秒为单位的 32 位周期。 2^31 毫秒相当于 24.85 天。还有一个重载允许您使用 64 位有符号整数指定延迟。 2^63 毫秒是……几亿年。

但是让这些计时器在遥远的未来的确切日期/时间计时是有问题的,因为您必须指定延迟时间,并且时钟更改会导致您的延迟太短或太长(想想夏令时,或用户启动的时间更改)。您可以指定滴答时间并让计时器每秒轮询当前时间。这有点难看,但它确实有效。

另一个选项是使用 Windows Waitable Timer 对象,它可以让您设置准确的日期和时间。不幸的是,框架不包括相应的对象。几年前我写了一篇关于它的文章并发布了代码。文章不再在线提供,但您可以从http://mischel.com/pubs/waitabletimer.zip下载代码。

【讨论】:

  • 感谢您的回答。我同意让计时器每秒轮询当前时间有点难看,但是如果我编写一个动态轮询计时器呢?例如,如果到期日期时间超过 12 小时,它只能每小时轮询一次时间。然后,如果到期日期时间超过 3 小时,它只能每 30 分钟轮询一次时间,依此类推...
  • @gilgha:你是否每秒轮询,或者使用某种算法来调整轮询频率并不重要。除非您的机器严重超载,否则资源使用确实不是问题。轮询就是轮询。在这种情况下,对它的反对主要是美学上的。
【解决方案2】:

我过去曾使用过名为 ManualResetEvent 的东西并取得了很大的成功。假设您知道运行代码的确切时间,那么您将能够计算预计运行时间 (TTR),并且应在第一次运行时配置后续运行。

partial class SomeService : ServiceBase
{
    private ManualResetEvent stop = new ManualResetEvent(false);
    private List<DateTime> times;
    private int idxDT = 0;

    public SomeService()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        this.stop.Reset();

        //implement you logic to calculate miliseconds to desired first run time
        int miliseconds_to_run = 1;

        ThreadPool.RegisterWaitForSingleObject(this.stop, 
                                               new WaitOrTimerCallback(ThreadFunc),
                                               null,
                                               miliseconds_to_run, 
                                               true);

    }

    private void ThreadFunc(object _state, bool _timedOut)
    {
        if (_timedOut)
        {
            if(this.times == null)
            {
                //get a list of times to run, store it along with the index of current TTR
                this.times = new List<DateTime>();
            }

            int miliseconds_to_run = (this.times[this.idxDT++] - DateTime.Now).Miliseconds;

            ThreadPool.RegisterWaitForSingleObject(this.stop,
                                                   new WaitOrTimerCallback(ThreadFunc),
                                                   null,
                                                   miliseconds_to_run,
                                                   true);
       }
    }


    protected override void OnStop()
    {
        this.stop.Set();
    }
}

当然,这在很大程度上取决于您的工作开始时间必须有多准确。 ThreadPool 类将向操作系统发送一个线程分配请求,然后它将等待来自池的下一个可用线程。在某些具有大量线程的进程中,这可能会导致线程不足,并且您的确切时间会迟到。

您也可以尝试从 .NET 创建任务计划程序作业,但我以前从未这样做过。

【讨论】:

    【解决方案3】:

    即使你说你不想使用System.Timers.Timer,我还是会告诉你怎么做,因为我认为你问题中的第 1 条和第 2 条似乎不是反对使用它的有效点.

    这就是我在我的几个应用程序中所做的:

    // 1. Create the timer
    System.Timers.Timer timer = new System.Timers.Timer();
    timer.AutoReset = false;
    timer.Elapsed += ...;
    
    // 2. Calculate the number of milliseconds between the scheduled time and the current time
    timer.Interval = (scheduledDate - DateTime.Now).TotalMilliSeconds;
    
    // 3. Start the timer
    timer.Start();
    

    完成。该事件将在所需时间“大致”触发。请注意,这不能精确到毫秒,因为 Windows 不是实时操作系统。

    【讨论】:

      【解决方案4】:

      我确实遇到过类似的问题,并使用了以下可能解决您的问题的解决方案。

      我使用了 ManualResetEvent 类,添加了一个 Wait 方法并让服务等待准确的时间。

      protected ManualResetEvent waitEvent = new ManualResetEvent(false);
      
      protected bool Wait(int milliSecs)
          {
              return !this.waitEvent.WaitOne(milliSecs, true);
          }
      

      我在 OnStart 方法中使用了这个等待如下:

       protected override void OnStart(string[] args)
          {
      
              DateTime nextRun = dt;//datetime from the database
      
              YourProcessOrThreadToRun process = new YourProcessOrThreadToRun();
              while (Wait((int)nextRun.Subtract(DateTime.Now).TotalMilliseconds))
              {
                      process.StartProcess();                
              }
          } 
      

      YourProcessOrThreadToRun 是您要在该确切时间运行的线程。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-30
        • 1970-01-01
        • 1970-01-01
        • 2013-11-25
        • 2014-02-13
        相关资源
        最近更新 更多