我之前在这个帖子中问了一个问题Timed events overlapping during execution 我得到的答案并没有像我预期的那样真正解决。我根据msdn https://msdn.microsoft.com/en-us/library/system.threading.interlocked(v=vs.110).aspx文章对它进行了一些修改,但它确实有效。或者我认为。 这是我修改过的代码
public partial class Service1 : ServiceBase
{
private Timer timer1 = null;
private long isTaskRunning = 0;
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
timer1 = new Timer();
this.timer1.Interval = 1000;
this.timer1.Elapsed += new System.Timers.ElapsedEventHandler(this.timer1_Tick);
timer1.Enabled = true;
Library.WriteErrorLog("service has started");
}
private void timer1_Tick(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Acquiring Lock");
try
{
if (Interlocked.Exchange(ref isTaskRunning, 1)==1)
{
Console.WriteLine("Lock Denied");
return;
}
else
{
Console.WriteLine("Lock Acquired");
//retrieve data from database
//read rows
//Loop through rows
//send values through network
//receive response and update db
Interlocked.Exchange(ref isTaskRunning, 0);
}
}
catch (Exception ex)
{
Library.WriteErrorLog(ex);
}
}
}
protected override void OnStop()
{
timer1.Enabled = false;
Library.WriteErrorLog("service has stopped");
}
}
我现在的问题是,这段代码是否是线程安全的? 这个代码是否有可能崩溃。 该代码应该在虚拟专用服务器上运行,并且是业务关键系统的一部分。任何帮助将不胜感激。
答案 0 :(得分:1)
首先,您需要Interlocked.CompareExchange,而不是Interlocked.Exchange
。如果您使用Interlocked.Exchange
,您的代码将允许每隔一段时间重入。并且可能无法允许代码在应该运行时运行。
我不知道您是否打算这样做,但如果您的处理引发异常,那么isTaskRunning
永远不会重置为0.
使用Monitor:
,您可以轻松完成同样的事情private object _timerLock = new object();
private void timer1_Tick(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Acquiring Lock");
if (!Monitor.TryEnter(_timerLock))
{
Console.WriteLine("Lock Denied");
return;
}
try
{
Console.WriteLine("Lock Acquired");
//retrieve data from database
//read rows
//Loop through rows
//send values through network
//receive response and update db
Monitor.Exit(_timerLock);
}
catch (Exception ex)
{
Library.WriteErrorLog(ex);
}
}
同样,如果抛出异常,这将继续保持锁定。您可以将Monitor.Exit
移动到finally
块,但这会引发其他问题。见Eric Lippert的Locks and exceptions do not mix。使用Interlocked.CompareExchange
进行锁定不会改变基本问题。
答案 1 :(得分:1)
在阅读Jim Mischel的回答并阅读链接的帖子(Locks and exceptions do not mix)之后,我整理了一个假设的模型,说明这可能有用。
我建议设置计时器和处理事件的类与调用任何行为的事件分开。更进一步,也许ComponentUsed
类(对该名称没有太多考虑)本身应该只是一个调用某些实际行为的类的包装器。
这使责任分开。一个类负责在计时器过去时做某事,而不知道锁和失败状态。如果必须在除该计时器事件之外的某处复制行为,则这很重要。
锁定,处理异常以及确定/了解组件是否处于故障状态的责任是独立的。
在这种情况下,如果组件发生故障并进入失败状态,后续调用不应被处理,则会引发EnteredFailedState
事件。 RunsOnTimer
可以通过停止计时器并做其他必须做的其他事情来响应这一点。如果它是Windows服务,它可能会记录异常并关闭。
但是,计时器是否继续流逝,内部组件正在管理其自身失败状态的责任。它知道它不应该继续处理请求。
这只是概念性的,但如果我有这样一个场景,我必须同时锁定和处理异常,我可能会在此基础上构建。如果进程可以安全地继续进行,即使锁中的进程引发异常也没有多大关系。
public class RunsOnTimer
{
private readonly Timer _timer = new Timer(1000);
private readonly ComponentUsed _component = new ComponentUsed();
public RunsOnTimer()
{
_component.EnteredFailedState += Component_EnteredFailedState;
_timer.Elapsed += _timer_Elapsed;
}
private void Component_EnteredFailedState(ComponentUsed sender, EnteredFailedStateEventArgs e)
{
_timer.Stop();
}
private void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
_component.DoSomething();
}
}
// Some component containing the actual behavior being invoked
// when the timer elapses.
public class ComponentUsed
{
private readonly object _doingSomethingLock = new object();
private bool _failedState;
public event EnteredFailedStateEventHandler EnteredFailedState;
public void DoSomething()
{
if (_failedState) return; //Perhaps throw an exception.
if (!Monitor.TryEnter(_doingSomethingLock)) return;
try
{
// Do whatever the component does
}
catch (Exception ex)
{
// Is the exception fatal? Should we stop executing this?
EnterFailedState(ex);
}
finally
{
Monitor.Exit(_doingSomethingLock);
}
}
private void EnterFailedState(Exception ex)
{
_failedState = true;
if (EnteredFailedState != null) EnteredFailedState(this, new EnteredFailedStateEventArgs(ex));
}
}