Non-reentrant timers

后端 未结 5 462
别那么骄傲
别那么骄傲 2020-12-11 04:30

I have a function that I want to invoke every x seconds, but I want it to be thread-safe.

Can I set up this behavior when I am creating the timer? (I don\'t

相关标签:
5条回答
  • 2020-12-11 04:56

    How timer could know about your shared data?

    Timer callback is executed on some ThreadPool thread. So you will have at least 2 threads:

    1. Your main thread where timer is created and launched;
    2. Thread from ThreadPool for launching callback.

    And it is your responsibility to provide correct work with your shared data.

    Re edits: chibacity provided the perfect example.

    0 讨论(0)
  • 2020-12-11 04:59

    I'm guessing, as your question is not entirely clear, that you want to ensure that your timer cannot re-enter your callback whilst you are processing a callback, and you want to do this without locking. You can achieve this using a System.Timers.Timer and ensuring that the AutoReset property is set to false. This will ensure that you have to trigger the timer on each interval manually, thus preventing any reentrancy:

    public class NoLockTimer : IDisposable
    {
        private readonly Timer _timer;
    
        public NoLockTimer()
        {
            _timer = new Timer { AutoReset = false, Interval = 1000 };
    
            _timer.Elapsed += delegate
            {
                //Do some stuff
    
                _timer.Start(); // <- Manual restart.
            };
    
            _timer.Start();
        }
    
        public void Dispose()
        {
            if (_timer != null)
            {
                _timer.Dispose();
            }
        }
    } 
    
    0 讨论(0)
  • 2020-12-11 05:03
    using System;
    using System.Diagnostics;
    
    /// <summary>
    ///     Updated the code.
    /// </summary>
    public class NicerFormTimer : IDisposable {
    
        public void Dispose() {
            using ( this.Timer ) { }
    
            GC.SuppressFinalize( this );
        }
    
        private System.Windows.Forms.Timer Timer { get; }
    
        /// <summary>
        ///     Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
        /// </summary>
        /// <param name="action"></param>
        /// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
        /// <param name="milliseconds"></param>
        public NicerFormTimer( Action action, Boolean repeat, Int32? milliseconds = null ) {
            if ( action == null ) {
                return;
            }
    
            this.Timer = new System.Windows.Forms.Timer {
                Interval = milliseconds.GetValueOrDefault( 1000 )
            };
    
            this.Timer.Tick += ( sender, args ) => {
                try {
                    this.Timer.Stop();
                    action();
                }
                catch ( Exception exception ) {
                    Debug.WriteLine( exception );
                }
                finally {
                    if ( repeat ) {
                        this.Timer.Start();
                    }
                }
            };
    
            this.Timer.Start();
        }
    
    }
    
    /// <summary>
    ///     Updated the code.
    /// </summary>
    public class NicerSystemTimer : IDisposable {
    
        public void Dispose() {
            using ( this.Timer ) { }
    
            GC.SuppressFinalize( this );
        }
    
        private System.Timers.Timer Timer { get; }
    
        /// <summary>
        ///     Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
        /// </summary>
        /// <param name="action"></param>
        /// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
        /// <param name="milliseconds"></param>
        public NicerSystemTimer( Action action, Boolean repeat, Double? milliseconds = null ) {
            if ( action == null ) {
                return;
            }
    
            this.Timer = new System.Timers.Timer {
                AutoReset = false,
                Interval = milliseconds.GetValueOrDefault( 1000 )
            };
    
            this.Timer.Elapsed += ( sender, args ) => {
                try {
                    this.Timer.Stop();
                    action();
                }
                catch ( Exception exception ) {
                    Debug.WriteLine( exception );
                }
                finally {
                    if ( repeat ) {
                        this.Timer.Start();
                    }
                }
            };
    
            this.Timer.Start();
        }
    
    }
    
    0 讨论(0)
  • 2020-12-11 05:07

    I know I can implement locks inside my callback function, but I think it will be more elegant if it will be in the timer level

    If locking is necessary then how could a timer arrange that? You're looking for a magical freebie.

    Re Edit1:

    Your choices are System.Timers.Timer and System.Threading.Timer, both need precautions against re-entrance. See this page and look for the Dealing with Timer Event Reentrance section.

    0 讨论(0)
  • 2020-12-11 05:18

    Complementing Tim Lloyd's solution for System.Timers.Timer, here's a solution to prevent reentrancy for cases where you want to use System.Threading.Timer instead.

    TimeSpan DISABLED_TIME_SPAN = TimeSpan.FromMilliseconds(-1);
    
    TimeSpan interval = TimeSpan.FromSeconds(1);
    Timer timer = null; // assign null so we can access it inside the lambda
    
    timer = new Timer(callback: state =>
    {
      doSomeWork();
      try
      {
        timer.Change(interval, DISABLED_TIME_SPAN);
      }
      catch (ObjectDisposedException timerHasBeenDisposed)
      {
      }
    }, state: null, dueTime: interval, period: DISABLED_TIME_SPAN);
    

    I believe you don't want interval to be accessed inside of the callback, but that is be easy to fix, if you want to: Put the above into a NonReentrantTimer class that wraps the BCL's Timer class. You would then pass the doSomeWork callback in as a parameter. An example of such a class:

    public class NonReentrantTimer : IDisposable
    {
        private readonly TimerCallback _callback;
        private readonly TimeSpan _period;
        private readonly Timer _timer;
    
        public NonReentrantTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
        {
            _callback = callback;
            _period = period;
            _timer = new Timer(Callback, state, dueTime, DISABLED_TIME_SPAN);
        }
    
        private void Callback(object state)
        {
            _callback(state);
            try
            {
                _timer.Change(_period, DISABLED_TIME_SPAN);
            }
            catch (ObjectDisposedException timerHasBeenDisposed)
            {
            }
        }
    
    
        public void Dispose()
        {
            _timer.Dispose();
        }
    }
    
    0 讨论(0)
提交回复
热议问题