线程安全定时事件

时间:2016-08-05 16:30:11

标签: c# multithreading service timer

我之前在这个帖子中问了一个问题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");
    }
}

我现在的问题是,这段代码是否是线程安全的? 这个代码是否有可能崩溃。 该代码应该在虚拟专用服务器上运行,并且是业务关键系统的一部分。任何帮助将不胜感激。

2 个答案:

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