每天在特定时间执行方法的代码 C# (Windows 服务)失败

6
我有一个代码,用于在每天早上5点执行Windows服务的方法。
编辑:
MyService ws = new MyService ();

protected override void OnStart(string[] args)
{
    if (serviceHost != null)
    {
        serviceHost.Close();
    }

    serviceHost = new ServiceHost(typeof(MyService));

    serviceHost.Open();
    double TimeOfExecution = 5;

    DateTime now = DateTime.Now;
    DateTime today5am = now.Date.AddHours(TimeOfExecution);
    DateTime next5am = now <= today5am ? today5am : today5am.AddDays(1);

    System.Threading.TimerCallback callback = new System.Threading.TimerCallback(ws.MethodToExecute());

    var timer1 = new System.Threading.Timer(callback, null, next5am - DateTime.Now, TimeSpan.FromHours(24));

 }

我原本期待该服务在下一个5点执行,并且之后每隔24小时再次执行。
方法MethodToExecute()确实在当天的5点(或其他指定时间)得到了执行,但第二天就没有执行。此外,似乎无论我是否第一次执行它都没问题,但是看起来服务会在一段时间后进入休眠状态,不再执行,所以如果5点在下一天而非当天到达,它将无法执行。
有人知道可能出了什么问题吗?

1
你能发更多的代码吗?具体来说,你确定你的计时器能够存活到第二天吗?使用var表示局部变量... - nvoigt
请注意,此程序不考虑时间更改。如果您的地区使用夏令时,则可能会出现时间变为早上6点或早上4点的情况。 - kakridge
如果您希望在特定时间运行它,而不是将其作为服务,您可以考虑将其制作为普通控制台应用程序,并使用Windows任务计划程序运行它。 - MarceloBarbosa
1
@MarceloBarbosa 我的服务中有几个方法在不同时间运行并执行不同的任务(一个方法在上午8点运行,第二个在下午3点运行,第三个在晚上9点运行),所以我认为这样设置比制作三个在不同时间运行并执行不同任务的控制台应用程序更容易,特别是如果你有很多方法的话,我不知道,也许这只是个人偏好... - vldmrrdjcc
4个回答

10

由于您在 OnStart 方法之后没有对其进行引用,因此GC将收集您的 timer

您只是将其作为局部变量。我希望您知道,一旦JIT表示在代码中不再使用它们,局部变量就有资格进行垃圾回收。

解决方案:只需将 timer 存储在实例变量中,即可完成。

private System.Threading.Timer my5AmTimer = null;

protected override void OnStart(string[] args)
{
    //All other code..

   this.my5AmTimer = new System.Threading.Timer(callback, null, next5am - DateTime.Now, TimeSpan.FromHours(24));
}

谢谢!但是,如果计时器在OnStart方法结束后立即被垃圾回收,那么第一次执行该方法是如何可能的呢? - vldmrrdjcc
@user3048706 我希望你知道本地变量一旦JIT表示它们在代码中不再被使用,就可以进行垃圾回收处理。 它是有资格参与GC的。GC在需要时运行,而不是始终运行。建议阅读手册以获取更多信息。 - ta.speot.is
@ta.speot.is 你应该再读一遍我的陈述。我从未说过 GC 总是在运行。我说它是有资格进行垃圾回收,这是正确的。有任何评论吗? - Sriram Sakthivel
@ta.speot.is 你应该再读一遍我的声明。我从未说过GC一直在运行。我说它有资格进行垃圾回收,这是正确的。有任何评论吗?-Sriram Sakthivel 1分钟前 @SriramSakthivel 请阅读我的评论。特别是我说“GC在需要时运行”和“它不会一直运行”的部分。特别注意我引用你的评论的部分,并强调“有资格”这个词。 - ta.speot.is
@ta.speot.is 对不起,我误解了你的意思。所以你是给OP提供更多信息对吗? - Sriram Sakthivel
显示剩余3条评论

5
var timer1 = new System.Threading.Timer(...);

计时器是一个棘手的对象。与任何.NET对象一样,当它们不再被引用时,它们也会受到垃圾回收的影响。这个语句的语法足以知道发生这种情况时,您使用的var表明"timer1"是一个方法的本地变量。换句话说,在方法返回后,没有任何引用指向您创建的计时器对象。
那么究竟是什么让计时器在没有引用的情况下保持存活并继续滴答声?你找到了答案:没有任何东西。无论您的计时器是否实际上会滴答声都是随机的。如果您的程序在执行其他操作时继续运行,则会触发gen#0收集,计时器将消失。它永远不会滴答声。如果您的程序没有运行,则它将存活足够长的时间以便于在早上5点时到达。
您必须将对象引用存储在确保其足够长时间存活的变量中。这应该是一个静态变量。
或者使用System.Timers.Timer。它的行为很像System.Threading.Timer,但具有更好的生存能力。只要它具有Elapsed事件处理程序并且已启用,它就保证保持存活。

@Hans Passant感谢您的帮助!我的MyService ws = new MyService();变量会存活多久?我需要将它也声明为静态吗? - vldmrrdjcc
不可能猜测。不要随意将变量设为静态的。听起来那个计时器应该是MyService中的一个字段,这样它就会在服务存活的同时保持活动状态。 - Hans Passant

0

另一个选择可能是告诉垃圾收集器保持局部变量的存活:

GC.KeepAlive(timer1);

这是不正确的。KeepAlive 不会使本地变量超越其声明的范围,它只会确保对象在调用 KeepAlive 后不会被垃圾回收。 - ferc

0
阅读精细手册

注意

只要您正在使用计时器,就必须保留对它的引用。与任何托管对象一样,当没有对计时器的引用时,它就会受到垃圾回收的影响。计时器仍然处于活动状态并不会防止其被回收。

保留对它的引用:

MyService ws = new MyService ();

System.Threading.Timer timer;

protected override void OnStart(string[] args)
{
    ...

    if (timer1 != null)
        timer1.Dispose();

    timer1 = new System.Threading.Timer(callback, null,
        next5am - DateTime.Now, TimeSpan.FromHours(24));
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接