【问题标题】:Thread Safe Timed Events线程安全的定时事件
【发布时间】:2016-08-05 16:30:11
【问题描述】:

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

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

【问题讨论】:

    标签: c# multithreading service timer


    【解决方案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 进行锁定不会改变根本问题。

    【讨论】:

    • 感谢您的回答@Jim Mischel。不过,我觉得好像我刚刚打开了一个潘多拉的盒子。我想问一下,如果我在ElaspsedEvent 处理程序中停止并重新启动计时器,这会起作用吗?正如这篇文章中提到的stackoverflow.com/questions/38656614/…`
    • @elfico:是的,停止并重新启动计时器是一种很好的做事方式。这消除了锁定问题,并防止重新进入。另外,请注意System.Timers.Timer:它会吞下异常。请参阅blog.mischel.com/2011/05/19/… 了解更多详情。
    • 非常感谢@Jim Mischel。我只是想问一下System.Threading.Timer 是否会是一个更好的选择。我在 Stackoverflow 上读过很多帖子说 System.Threading.Timer 不是线程安全的。
    • @elfico:System.Timers.Timer 是围绕System.Threading.Timer 的组件包装器。包装器添加了事件接口,还添加了一个SynchronizingObject 成员,可让您自动与 UI 线程同步。您可以通过调用DispatchInvoke 来自己使用System.Threading.Timer 进行同步。两个计时器都是“线程安全的”。请记住,System.Threading.Timer 的回调在单独的线程上执行。 System.Timers.TimerElapsed 事件通常在单独的线程上执行,但可以在 UI 线程上执行。
    • 感谢您到目前为止的帮助。我尝试启动和停止计时器,但它没有用。我使用了Monitor.TryEnter,效果很好。但是我有一次,我得到A transport-level error has occurred when sending the request to the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) 错误并且锁被持有了很长时间。当它被释放时,它会工作一段时间,然后再次被持有。请问我怎样才能最好地处理这个问题。
    【解决方案2】:

    在阅读了 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));
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2010-11-05
      • 1970-01-01
      • 1970-01-01
      • 2017-04-21
      • 2022-01-14
      • 1970-01-01
      • 2011-03-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多