System.Timers Windows服务。计时器不解雇

时间:2022-02-19 20:33:58

I have a Windows service written in C# which is meant to perform a task every few minutes. I'm using a System.Timers.Timer for this but it doesn't ever appear to fire. I've looked at many different posts here on SO and elsewhere and I'm not seeing what is wrong with my code.

我有一个用c#编写的Windows服务,目的是每隔几分钟执行一次任务。我使用System.Timers。计时器,但它从来不会开火。我在这里和其他地方看过很多不同的文章,我没有发现我的代码有什么问题。

Here is my code, with non-timer related items removed for clarity...

这是我的代码,为了清晰起见,删除了非定时器相关的项目……

namespace NovaNotificationService
{
    public partial class NovaNotificationService : ServiceBase
    {
        private System.Timers.Timer IntervalTimer;
        public NovaNotificationService()
        {
            InitializeComponent();
            IntervalTimer = new System.Timers.Timer(60000);  // Default in case app.config is silent.
            IntervalTimer.Enabled = false;
            IntervalTimer.Elapsed += new ElapsedEventHandler(this.IntervalTimer_Elapsed);
        }

        protected override void OnStart(string[] args)
        {
            // Set up the timer...
            IntervalTimer.Enabled = false;
            IntervalTimer.Interval = Properties.Settings.Default.PollingFreqInSec * 1000;
            // Start the timer and wait for the next work to be released...
            IntervalTimer.Start();
        }

        protected override void OnStop()
        {
            IntervalTimer.Enabled = false;
        }

        private void IntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {   // Do the thing that needs doing every few minutes...
            DoWork();
        }
    }
}

I'm really scratching my head over this one. Can anybody spot what silly thing I'm getting wrong?

这件事让我百思不得其解。有人能看出我做错了什么蠢事吗?

EDIT: By suggestion, I added IntervalTimer.Enabled = true; before IntervalTimer.Start(); in the service OnStart method. This doesn't resolve the issue.

编辑:根据建议,我添加了IntervalTimer。启用= true;之前IntervalTimer.Start();在服务OnStart方法中。这并不能解决问题。

I've added file trace logging into the service to confirm some of the internals and I know for sure that the Timer.Enabled value is true by the time OnStart() is finished.

我已经将文件跟踪日志记录添加到服务中,以确认其中的一些内部细节,并且我确信计时器。当启动()完成时,启用的值是正确的。

6 个解决方案

#1


45  

Here is my work-around...

这是我的工作…

After way too many hours searching for an answer to this, I discovered a wide variety of articles and blogs discussing timers in Windows services. I've seen a lot of opinions on this and they all fall into three categories and in descending order of frequency:

在花了太多的时间寻找答案之后,我发现了大量关于Windows服务中计时器的文章和博客。我在这方面见过很多观点,它们都可以分为三类,按频率的降序排列:

  1. Don't use System.Windows.Forms.Timer because it won't work. (this only makes sense)

    不要使用System.Windows.Forms。定时器因为它不能工作。(这才有意义)

  2. Don't use System.Threading.Timer because it doesn't work, use System.Timers.Timer instead.

    不要使用System.Threading。定时器因为它不工作,使用system .Timer。计时器。

  3. Don't use System.Timers.Timer because it doesn't work, use System.Threading.Timer instead.

    不要使用System.Timers。定时器,因为它不工作,使用System.Threading。计时器。

Based on this, I tried 2. This is also the approach that seems to be recommended by Microsoft since they say that System.Timers.Timer is suited to "Server applications".

基于此,我尝试了2。这也是微软推荐的方法,因为他们说system . timer。计时器适用于“服务器应用程序”。

What I've found is that System.Timers.Timer just doesn't work in my Windows Service application. Therefore I've switched over to System.Threading.Timer. It's a nuisance since it requires some refactoring to make it work.

我发现的是这个系统。定时器。Timer在我的Windows服务应用程序中不起作用。因此我切换到system . thread . timer。这是一个麻烦,因为它需要一些重构才能工作。

This is approximately what my working code looks like now...

这大概就是我现在的工作代码……

namespace NovaNotificationService
{
    public partial class NovaNotificationService : ServiceBase
    {
        private System.Threading.Timer IntervalTimer;
        public NovaNotificationService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            TimeSpan tsInterval = new TimeSpan(0, 0, Properties.Settings.Default.PollingFreqInSec);
            IntervalTimer = new System.Threading.Timer(
                new System.Threading.TimerCallback(IntervalTimer_Elapsed)
                , null, tsInterval, tsInterval);
        }

        protected override void OnStop()
        {
            IntervalTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            IntervalTimer.Dispose();
            IntervalTimer = null;
        }

        private void IntervalTimer_Elapsed(object state)
        {   // Do the thing that needs doing every few minutes...
            // (Omitted for simplicity is sentinel logic to prevent re-entering
            //  DoWork() if the previous "tick" has for some reason not completed.)
            DoWork();
        }
    }
}

I hate the "Doctor, doctor, it hurts when I do this..." solution, but that's what I had to resort to. One more opinion on the pile for the next guy with this problem...

我讨厌“医生,医生,当我这么做的时候会很疼…”再给下一个有这个问题的人一个意见……

#2


10  

You forget to enable timer by setting:

您忘记通过设置启用计时器:

IntervalTimer.Enabled = true;

or calling Start method:

或者调用Start方法:

IntervalTimer.Start();
protected override void OnStart(string[] args)
{
    // Set up the timer...
    IntervalTimer.Interval = Properties.Settings.Default.PollingFreqInSec * 1000;
    // Start the timer and wait for the next work to be released...
    IntervalTimer.Start();
}

#3


4  

Apparently, System.Timers.Timer hides any exceptions, swallows them quietly, and then chokes. Of course, you can handle these in your method that you've added as a handler to your timer, but if the exception is thrown immediately on entrance (before the first line of code is executed, which can happen if your method declares a variable that uses an object in a strong-named DLL of which you have the wrong version, for instance), you are never going to see that exception.

显然,System.Timers。定时器隐藏任何异常,悄悄地吞下它们,然后窒息。当然,你可以在你的方法处理这些,你作为一个处理程序添加你的计时器,但如果异常立即入口(在第一行代码被执行之前,也可能发生如果你的方法声明一个变量,使用一个对象在一个strong-named DLL的你有错误的版本,例如),你也永远不会看到,例外。

And you are going to join us all in tearing your hair out.

你会和我们一起把你的头发弄乱。

Or you could do this:

或者你可以这样做:

  • create a wrapper method that (in a try-catch loop) calls the method you would like to have executed. If this method is dying on you, the wrapped method can do the exception handling, without killing the timer, because if you do not stop the timer, it will never notice something went wrong.
  • 创建一个包装器方法(在try-catch循环中)调用您想要执行的方法。如果这个方法在您身上失效,包装的方法可以执行异常处理,而不会杀死计时器,因为如果您不停止计时器,它将永远不会注意到发生了什么错误。

(I did end up stopping the timer, because if it fails, trying again makes no sense for this particular application...)

(我最终停止了计时器,因为如果它失败了,再次尝试对于这个特定的应用程序来说是没有意义的……)

Hope this helps those who landed here from Google (as did I).

希望这能帮助那些从谷歌降落到这里的人(我也是)。

#4


3  

I also had to switch to System.Threading.Timer. To make re-factoring easier and others live easy, I created a separate class, containing an instance of System.Threading.Timer and has almost the same methods as System.Timers.Timer, so that calling code requires minimal changes:

我还必须切换到system . thread . timer。为了简化重构,使其他类更容易实现,我创建了一个单独的类,其中包含System.Threading的实例。定时器和system .Timer的方法几乎相同。定时器,因此调用代码需要最小的改变:

/// <summary>
/// Custom Timer class, that is actually a wrapper over System.Threading.Timer
/// </summary>
/// <seealso cref="System.IDisposable" />
internal class Timer : IDisposable
{
    System.Threading.Timer _timer;

    public Timer()
    {

    }
    public Timer(int interval) : this()
    {
        this.Interval = interval;
    }

    public bool AutoReset { get; set; }
    public bool Enabled { get; set; }
    public int Interval { get; set; }
    public Action<object> OnTimer { get; internal set; }

    public void Dispose()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            _timer.Dispose();
            _timer = null;
        }
    }

    public void Start()
    {
        _timer = new System.Threading.Timer(
            new System.Threading.TimerCallback(OnTimer), null, 0, Interval);
    }
    public void Stop()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
        }
    }
}

I hope this will help!

我希望这能有所帮助!

#5


2  

To add to what "user1820848" wrote, because that was my problem as well, if your System.timers.timer elapsed event doesn't seem to be firing, put everything in the event handler in a try/catch block, and look for any problem there. I tried all of the recommended methods to deal with this problem (or thought I had), including switching from system.timers.timer to system.threading.timer, and that didn't work either.

再加上"user1820848"因为这也是我的问题,如果你的system .timer。timer事件似乎没有被触发,将事件处理程序中的所有内容放到try/catch块中,并查找其中的任何问题。我尝试了所有推荐的方法来处理这个问题(或者认为我有),包括从system.timer中切换。system.threading计时器。定时器,也不管用。

I think the problem is compounded because many of us are moving our applications from our workstation, where we can attach to the running service and verify that it works, to a server where we don't have any debugging support. So you're stuck with event log messages or tracelistener messages, and it's completely odd that the event doesn't fire.

我认为这个问题更加复杂,因为我们中的许多人正在将我们的应用程序从我们的工作站上转移到一个我们没有任何调试支持的服务器上,在那里我们可以附加到正在运行的服务,并验证它是否有效。因此,您会被事件日志消息或tracelistener消息所困,而且事件不会触发是完全奇怪的。

I had a situation where I have three running services on this server, running essentially the same timer code. I even went line by line with another running service's code to make sure I was doing the system.timers.timer handling the same. But the other service works fine, and this one didn't seem to be firing the event at all.

我在这个服务器上有三个正在运行的服务,运行的是相同的定时器代码。我甚至用另一个正在运行的服务的代码一行一行地执行system.timer。定时器处理相同。但另一项服务运行得很好,而这一项似乎根本没有启动事件。

The problem, as it turned out, was that in my initial dim statements I was firing up a class that was trying to connect to Oracle. That call was failing, but it was actually failing because the Oracle client version on my workstation and server was slightly different. It happened when the CLR was resolving the references, so it wasn't caught in my underlying class try/catch blocks. If I were debugging, the debugger would have flagged the error. Running on the server, the CLR had no way to tell me about the problem. So my service just sat there on an untrapped error.

问题在于,在我最初的dim语句中,我启动了一个试图连接Oracle的类。这个调用失败了,但实际上失败了,因为我的工作站和服务器上的Oracle客户端版本稍有不同。当CLR解决了引用时,它就发生了,所以它并没有在我的底层类try/catch块中被捕获。如果我在调试,调试器会标记错误。在服务器上运行,CLR无法告诉我这个问题。所以我的服务只是在一个未被捕获的错误上。

Putting everything in a try/catch immediately pointed out the problem. Put your try before any declarations in that subroutine. If you're failing on a very early statement, that's how you'll catch it.

把所有的东西放在一起试一试,马上就指出了问题所在。将try放在该子例程中的任何声明之前。如果你在早期的陈述上失败了,你就会发现它。

[Sorry for the separate answer, but you have to provide answers to get enough reputation to even comment on someone else's answer?!?]

(不好意思单独回答,但你必须提供答案,以获得足够的声誉,甚至评论别人的答案?!)

[Edit: another thing to try is take your code out of the timer event, put it into another sub/function, call that from your startup code, and also put the function call in your timer event. Weeks later, back at my workstation, trying to run the same code, and I have that sinking feeling that my timer event isn't getting called, and I've been here before. Indeed! But putting everything in a try/catch isn't working either!?! Moved it to a function call and Bam, there's my exception - Oracle again. But it wasn't coming up even with every single line inside a try/catch, until I moved the code out of the timer event and tried again.]

[编辑:另一件需要尝试的事情是将代码从计时器事件中取出,将其放入另一个子/函数中,从启动代码中调用它,并将函数调用放入计时器事件中。几周后,回到我的工作站上,试着运行相同的代码,我有一种不祥的感觉,我的计时器事件没有被调用,我以前也曾到过这里。确实!但是把所有的东西都放在尝试中也不管用!将它移动到一个函数调用和Bam,这是我的异常——Oracle。但即使在try/catch的每一行中,它也不会出现,直到我将代码移出计时器事件并再次尝试。

#6


-2  

 private void IntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {   // Do the thing that needs doing every few minutes...
        DoWork();

        //Add following 2 lines. It will work.
        **IntervalTimer.Interval= 100; //any value
        IntervalTimer.Start();**
    }

#1


45  

Here is my work-around...

这是我的工作…

After way too many hours searching for an answer to this, I discovered a wide variety of articles and blogs discussing timers in Windows services. I've seen a lot of opinions on this and they all fall into three categories and in descending order of frequency:

在花了太多的时间寻找答案之后,我发现了大量关于Windows服务中计时器的文章和博客。我在这方面见过很多观点,它们都可以分为三类,按频率的降序排列:

  1. Don't use System.Windows.Forms.Timer because it won't work. (this only makes sense)

    不要使用System.Windows.Forms。定时器因为它不能工作。(这才有意义)

  2. Don't use System.Threading.Timer because it doesn't work, use System.Timers.Timer instead.

    不要使用System.Threading。定时器因为它不工作,使用system .Timer。计时器。

  3. Don't use System.Timers.Timer because it doesn't work, use System.Threading.Timer instead.

    不要使用System.Timers。定时器,因为它不工作,使用System.Threading。计时器。

Based on this, I tried 2. This is also the approach that seems to be recommended by Microsoft since they say that System.Timers.Timer is suited to "Server applications".

基于此,我尝试了2。这也是微软推荐的方法,因为他们说system . timer。计时器适用于“服务器应用程序”。

What I've found is that System.Timers.Timer just doesn't work in my Windows Service application. Therefore I've switched over to System.Threading.Timer. It's a nuisance since it requires some refactoring to make it work.

我发现的是这个系统。定时器。Timer在我的Windows服务应用程序中不起作用。因此我切换到system . thread . timer。这是一个麻烦,因为它需要一些重构才能工作。

This is approximately what my working code looks like now...

这大概就是我现在的工作代码……

namespace NovaNotificationService
{
    public partial class NovaNotificationService : ServiceBase
    {
        private System.Threading.Timer IntervalTimer;
        public NovaNotificationService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            TimeSpan tsInterval = new TimeSpan(0, 0, Properties.Settings.Default.PollingFreqInSec);
            IntervalTimer = new System.Threading.Timer(
                new System.Threading.TimerCallback(IntervalTimer_Elapsed)
                , null, tsInterval, tsInterval);
        }

        protected override void OnStop()
        {
            IntervalTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            IntervalTimer.Dispose();
            IntervalTimer = null;
        }

        private void IntervalTimer_Elapsed(object state)
        {   // Do the thing that needs doing every few minutes...
            // (Omitted for simplicity is sentinel logic to prevent re-entering
            //  DoWork() if the previous "tick" has for some reason not completed.)
            DoWork();
        }
    }
}

I hate the "Doctor, doctor, it hurts when I do this..." solution, but that's what I had to resort to. One more opinion on the pile for the next guy with this problem...

我讨厌“医生,医生,当我这么做的时候会很疼…”再给下一个有这个问题的人一个意见……

#2


10  

You forget to enable timer by setting:

您忘记通过设置启用计时器:

IntervalTimer.Enabled = true;

or calling Start method:

或者调用Start方法:

IntervalTimer.Start();
protected override void OnStart(string[] args)
{
    // Set up the timer...
    IntervalTimer.Interval = Properties.Settings.Default.PollingFreqInSec * 1000;
    // Start the timer and wait for the next work to be released...
    IntervalTimer.Start();
}

#3


4  

Apparently, System.Timers.Timer hides any exceptions, swallows them quietly, and then chokes. Of course, you can handle these in your method that you've added as a handler to your timer, but if the exception is thrown immediately on entrance (before the first line of code is executed, which can happen if your method declares a variable that uses an object in a strong-named DLL of which you have the wrong version, for instance), you are never going to see that exception.

显然,System.Timers。定时器隐藏任何异常,悄悄地吞下它们,然后窒息。当然,你可以在你的方法处理这些,你作为一个处理程序添加你的计时器,但如果异常立即入口(在第一行代码被执行之前,也可能发生如果你的方法声明一个变量,使用一个对象在一个strong-named DLL的你有错误的版本,例如),你也永远不会看到,例外。

And you are going to join us all in tearing your hair out.

你会和我们一起把你的头发弄乱。

Or you could do this:

或者你可以这样做:

  • create a wrapper method that (in a try-catch loop) calls the method you would like to have executed. If this method is dying on you, the wrapped method can do the exception handling, without killing the timer, because if you do not stop the timer, it will never notice something went wrong.
  • 创建一个包装器方法(在try-catch循环中)调用您想要执行的方法。如果这个方法在您身上失效,包装的方法可以执行异常处理,而不会杀死计时器,因为如果您不停止计时器,它将永远不会注意到发生了什么错误。

(I did end up stopping the timer, because if it fails, trying again makes no sense for this particular application...)

(我最终停止了计时器,因为如果它失败了,再次尝试对于这个特定的应用程序来说是没有意义的……)

Hope this helps those who landed here from Google (as did I).

希望这能帮助那些从谷歌降落到这里的人(我也是)。

#4


3  

I also had to switch to System.Threading.Timer. To make re-factoring easier and others live easy, I created a separate class, containing an instance of System.Threading.Timer and has almost the same methods as System.Timers.Timer, so that calling code requires minimal changes:

我还必须切换到system . thread . timer。为了简化重构,使其他类更容易实现,我创建了一个单独的类,其中包含System.Threading的实例。定时器和system .Timer的方法几乎相同。定时器,因此调用代码需要最小的改变:

/// <summary>
/// Custom Timer class, that is actually a wrapper over System.Threading.Timer
/// </summary>
/// <seealso cref="System.IDisposable" />
internal class Timer : IDisposable
{
    System.Threading.Timer _timer;

    public Timer()
    {

    }
    public Timer(int interval) : this()
    {
        this.Interval = interval;
    }

    public bool AutoReset { get; set; }
    public bool Enabled { get; set; }
    public int Interval { get; set; }
    public Action<object> OnTimer { get; internal set; }

    public void Dispose()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            _timer.Dispose();
            _timer = null;
        }
    }

    public void Start()
    {
        _timer = new System.Threading.Timer(
            new System.Threading.TimerCallback(OnTimer), null, 0, Interval);
    }
    public void Stop()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
        }
    }
}

I hope this will help!

我希望这能有所帮助!

#5


2  

To add to what "user1820848" wrote, because that was my problem as well, if your System.timers.timer elapsed event doesn't seem to be firing, put everything in the event handler in a try/catch block, and look for any problem there. I tried all of the recommended methods to deal with this problem (or thought I had), including switching from system.timers.timer to system.threading.timer, and that didn't work either.

再加上"user1820848"因为这也是我的问题,如果你的system .timer。timer事件似乎没有被触发,将事件处理程序中的所有内容放到try/catch块中,并查找其中的任何问题。我尝试了所有推荐的方法来处理这个问题(或者认为我有),包括从system.timer中切换。system.threading计时器。定时器,也不管用。

I think the problem is compounded because many of us are moving our applications from our workstation, where we can attach to the running service and verify that it works, to a server where we don't have any debugging support. So you're stuck with event log messages or tracelistener messages, and it's completely odd that the event doesn't fire.

我认为这个问题更加复杂,因为我们中的许多人正在将我们的应用程序从我们的工作站上转移到一个我们没有任何调试支持的服务器上,在那里我们可以附加到正在运行的服务,并验证它是否有效。因此,您会被事件日志消息或tracelistener消息所困,而且事件不会触发是完全奇怪的。

I had a situation where I have three running services on this server, running essentially the same timer code. I even went line by line with another running service's code to make sure I was doing the system.timers.timer handling the same. But the other service works fine, and this one didn't seem to be firing the event at all.

我在这个服务器上有三个正在运行的服务,运行的是相同的定时器代码。我甚至用另一个正在运行的服务的代码一行一行地执行system.timer。定时器处理相同。但另一项服务运行得很好,而这一项似乎根本没有启动事件。

The problem, as it turned out, was that in my initial dim statements I was firing up a class that was trying to connect to Oracle. That call was failing, but it was actually failing because the Oracle client version on my workstation and server was slightly different. It happened when the CLR was resolving the references, so it wasn't caught in my underlying class try/catch blocks. If I were debugging, the debugger would have flagged the error. Running on the server, the CLR had no way to tell me about the problem. So my service just sat there on an untrapped error.

问题在于,在我最初的dim语句中,我启动了一个试图连接Oracle的类。这个调用失败了,但实际上失败了,因为我的工作站和服务器上的Oracle客户端版本稍有不同。当CLR解决了引用时,它就发生了,所以它并没有在我的底层类try/catch块中被捕获。如果我在调试,调试器会标记错误。在服务器上运行,CLR无法告诉我这个问题。所以我的服务只是在一个未被捕获的错误上。

Putting everything in a try/catch immediately pointed out the problem. Put your try before any declarations in that subroutine. If you're failing on a very early statement, that's how you'll catch it.

把所有的东西放在一起试一试,马上就指出了问题所在。将try放在该子例程中的任何声明之前。如果你在早期的陈述上失败了,你就会发现它。

[Sorry for the separate answer, but you have to provide answers to get enough reputation to even comment on someone else's answer?!?]

(不好意思单独回答,但你必须提供答案,以获得足够的声誉,甚至评论别人的答案?!)

[Edit: another thing to try is take your code out of the timer event, put it into another sub/function, call that from your startup code, and also put the function call in your timer event. Weeks later, back at my workstation, trying to run the same code, and I have that sinking feeling that my timer event isn't getting called, and I've been here before. Indeed! But putting everything in a try/catch isn't working either!?! Moved it to a function call and Bam, there's my exception - Oracle again. But it wasn't coming up even with every single line inside a try/catch, until I moved the code out of the timer event and tried again.]

[编辑:另一件需要尝试的事情是将代码从计时器事件中取出,将其放入另一个子/函数中,从启动代码中调用它,并将函数调用放入计时器事件中。几周后,回到我的工作站上,试着运行相同的代码,我有一种不祥的感觉,我的计时器事件没有被调用,我以前也曾到过这里。确实!但是把所有的东西都放在尝试中也不管用!将它移动到一个函数调用和Bam,这是我的异常——Oracle。但即使在try/catch的每一行中,它也不会出现,直到我将代码移出计时器事件并再次尝试。

#6


-2  

 private void IntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {   // Do the thing that needs doing every few minutes...
        DoWork();

        //Add following 2 lines. It will work.
        **IntervalTimer.Interval= 100; //any value
        IntervalTimer.Start();**
    }