如何在C#的特定时间(远离现在)执行代码?

时间:2014-03-12 14:49:23

标签: c# multithreading datetime timer

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

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

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

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

我该如何实现?

谢谢。

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

再次感谢你。

4 个答案:

答案 0 :(得分:2)

您无法使用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下载代码。

答案 1 :(得分:1)

我过去使用了一些名为 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创建任务计划程序作业,但我以前从未这样做过。

答案 2 :(得分:1)

即使你说你不想使用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();

完成。该活动将大概开始#34;在期望的时间。请注意,由于Windows不是实时操作系统,因此无法精确到毫秒级。

答案 3 :(得分:1)

我确实面临类似问题,并使用下面的解决方案来解决您的问题。

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

protected ManualResetEvent waitEvent = new ManualResetEvent(false);

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

我在OnStart方法中使用了Wait,如下所示:

 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是您希望在该确切时间运行的线程。