要点: 在Windows服务和&控制台应用程序我正在调用一个包含Timer的公共库,该Timer会定期触发一个大约需要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应用程序main中调用它的方式:
TimedProcess.Instance.Start(1000);
这是在服务OnStop和应用程序main(挂起按键)中调用的方式:
TimedProcess.Instance.Stop();
答案 0 :(得分:9)
可能最简单,最可靠的方法是使用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
事件发生异常,它将永远不会逃脱,这意味着您永远不会知道它。有关详细信息,请参阅我的blog post。
答案 1 :(得分:0)
修改
System.Timers.Timer
的每个回调都在ThreadPool
上排队。请注意,System.Timers.Timer
可以具有竞争条件(您可以阅读有关它的更多信息here。)System.Threading.Timer
是一个稍微更好的包装器,我更喜欢使用它由于它的简单。
您没有详细说明您的特定应用程序是否可以处理该竞争条件,因此很难说清楚。但是根据您的代码,调用processTimer_Elapsed
后可能会有Stop()
排队的回调。
对于服务超时问题 -
执行此操作的一种方法是在超时时调用ServiceController
方法WaitForStatus
。我过去做过这个并且工作得相当好,虽然我记得有很多边缘情况需要等待很长时间。
答案 2 :(得分:0)
一种可能的替代方案似乎是不在计时器回调本身中进行实际工作,而是仅从胎面池中的工作项排队以完成工作。然后您可以继续处理计时器 - 当前在线程池上运行的任何内容都将保持运行,您的服务可以立即响应停止请求,但线程池项(如果已排队)仍将得到处理。