开发者

Waiting for a timer elapsed event to complete before application/service closes/stops

Summary: Within a Windows service & Console Application I am calling a common library that contains a Timer that periodically triggers an action that takes around 30 seconds to complete. This works fine, however...

When a service stop or application exit is called and the timer is in the ElapsedEventHandler I need the service stop/application exit to wait until the event handler has completed.

I have implemented this functionality by having a Boolean InEvent property that is checked when the开发者_如何学Go timer stop method is called.

While this is functional, the question is: Is this the best way to go about doing this? Is there an alternative approach that may serve this purpose better?

The other issue is that I need to avoid the service stop request failing with a "Service failed to respond to stop request"

This is my implementation

public sealed class TimedProcess : IDisposable
{
    static TimedProcess singletonInstance;
    bool InEvent;
    Timer processTimer;

    private TimedProcess()
    {
    }

    public static TimedProcess Instance
    {
        get
        {
            if (singletonInstance == null)
            {
                singletonInstance = new TimedProcess();
            }

            return singletonInstance;
        }
    }

    public void Start(double interval)
    {
        this.processTimer = new Timer();
        this.processTimer.AutoReset = false;
        this.processTimer.Interval = interval;
        this.processTimer.Elapsed += new ElapsedEventHandler(this.processTimer_Elapsed);
        this.processTimer.Enabled = true;
    }

    public void Stop()
    {
        if (processTimer != null)
        {
            while (InEvent)
            {
            }

            processTimer.Stop();
        }
    }

    void processTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            InEvent = true;
            // Do something here that takes ~30 seconds
        }
        catch
        {
        }
        finally
        {
            InEvent = false;
            processTimer.Enabled = true;
        }
    }

    public void Dispose()
    {
        if (processTimer != null)
        {
            Stop();
            processTimer.Dispose();
        }
    }
}

And this is how it is called in the service OnStart / console application main:

TimedProcess.Instance.Start(1000);

This is how it is called in service OnStop and application main (pending keypress):

TimedProcess.Instance.Stop();


Probably the easiest and most reliable way is to use a Monitor. Create an object that the main program and the timer callback can access:

private object _timerLock = new object();

Your main program tries to lock that before shutting down:

// wait for timer process to stop
Monitor.Enter(_timerLock);
// do shutdown tasks here

And your timer callback locks it, too:

void processTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    if (!Monitor.TryEnter(_timerLock))
    {
        // something has the lock. Probably shutting down.
        return;
    }
    try
    {
        // Do something here that takes ~30 seconds
    }
    finally
    {
        Monitor.Exit(_timerLock);
    }
}

The main program should never release the lock once it's obtained it.

If you want the main program to go ahead and shut down after some period of time, regardless of whether it's obtained the lock, use Monitor.TryEnter. For example, this will wait 15 seconds.

bool gotLock = Monitor.TryEnter(_timerLock, TimeSpan.FromSeconds(15));

The return value is true if it was able to obtain the lock.

By the way, I strongly suggest that you use System.Threading.Timer rather than System.Timers.Timer. The latter squashes exceptions, which can end up hiding bugs. If an exception occurs in your Elapsed event, it will never escape, meaning that you never know about it. See my blog post for more information.


EDIT

Each callback to the System.Timers.Timer is queued on the ThreadPool. Be aware that the System.Timers.Timer can have a race condition (you can read more about it here.) System.Threading.Timer is a slightly nicer wrapper which I prefer to use due to it's simplicity.

You haven't described enough details to know if your particular application could handle that race condition, so it's hard to tell. But given your code, it is possible that there might be a callback queued up for processTimer_Elapsed after Stop() is called.


For the service timeout issue --

One way to do this is to make a call to the ServiceController method WaitForStatus with a timeout. I've done this in the past and it works reasonably well, although I recall there being some edge cases around waiting for a very long time.

See the MSDN reference. A sample use is described here.


One possible alternative seems to be to not do the actual work in the timer callback itself but to just queue a work item from there on the tread pool to do the work. Then you can go ahead and dispose of the timer - anything currently running on the thread pool will remain operational, and your service can respond to the stop request immediately but the thread pool item (if queued) will still get processed.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜