在应用程序/服务关闭/停止之前等待计时器运行事件完成

本文关键字:计时器 等待 运行 事件 服务 应用程序 | 更新日期: 2023-09-27 18:05:30

摘要:在Windows服务&控制台应用程序我正在调用一个公共库,该库包含一个计时器,该计时器定期触发一个大约需要30秒才能完成的操作。然而,这很好用。。。

当调用服务停止或应用程序退出并且计时器在ElapsedEventHandler中时,我需要服务停止/应用程序退出等待事件处理程序完成。

我通过在调用计时器停止方法时检查布尔InEvent属性来实现此功能。

虽然这是有效的,但问题是:这是最好的方法吗?有没有其他方法可以更好地达到这一目的?

另一个问题是,我需要避免"服务未能响应停止请求"导致服务停止请求失败

这是我的实现

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();
        }
    }
}

这就是它在OnStart/console应用程序主服务中的调用方式:

TimedProcess.Instance.Start(1000);

这就是在服务OnStop和应用程序主(未决按键(中调用它的方式:

TimedProcess.Instance.Stop();

在应用程序/服务关闭/停止之前等待计时器运行事件完成

可能最简单、最可靠的方法是使用Monitor。创建一个主程序和计时器回调可以访问的对象:

private object _timerLock = new object();

你的主程序试图在关闭前锁定它:

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

你的定时器回调也会锁定它:

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);
    }
}

主程序一旦获得锁,就不应该释放它

如果希望主程序继续运行并在一段时间后关闭,无论它是否获得了锁定,请使用Monitor.TryEnter。例如,这将等待15秒。

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

如果能够获得锁,则返回值为true

顺便说一句,我强烈建议您使用System.Threading.Timer而不是System.Timers.Timer。后者会压制异常,这些异常最终可能会隐藏错误。如果Elapsed事件中发生异常,它将永远无法逃脱,这意味着你永远不会知道它。有关更多信息,请参阅我的博客文章。

编辑

System.Timers.Timer的每个回调都在ThreadPool上排队。请注意,System.Timers.Timer可以具有竞争条件(您可以在此处阅读更多信息。(System.Threading.Timer是一个稍微好一点的包装器,由于它的简单性,我更喜欢使用它。

您还没有描述足够的细节来了解您的特定应用程序是否可以处理这种竞争条件,所以很难说。但考虑到您的代码,在调用Stop()之后,可能会有一个回调排队等待processTimer_Elapsed


针对服务超时问题——

这样做的一种方法是在超时的情况下调用ServiceController方法WaitForStatus。我过去也这样做过,效果相当好,尽管我记得有一些边缘案例等待了很长时间。

请参阅MSDN参考资料。此处描述了一个示例用法。

一种可能的替代方案似乎是不在计时器回调本身中进行实际工作,而是将工作项从那里排队到线程池中进行工作。然后,您可以继续处理计时器-线程池中当前运行的任何东西都将保持可操作状态,您的服务可以立即响应停止请求,但线程池项目(如果已排队(仍将得到处理。