Search code examples
c#multithreadingtimervisual-studio-2013portable-class-library

Timer in portable class library


I've made a timer class based on this discussion but I have a problem with it, the elapsed event occurs only one time. The smaller issue: It would be a good thing if the _timer not have to be instantiated at every calling of Start().

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics.Contracts;

namespace JellystonePark.Model
{
    internal delegate void TimerCallback(object state);
    internal sealed class Timer : CancellationTokenSource, IDisposable
    {
        internal Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
        {
            Contract.Assert(period == TimeSpan.FromMilliseconds(-1), "This stub implementation only supports dueTime.");
            Task.Delay(dueTime, Token).ContinueWith((t, s) =>
            {
                var tuple = (Tuple<TimerCallback, object>)s;
                tuple.Item1(tuple.Item2);
            }, Tuple.Create(callback, state), CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
                TaskScheduler.Default);
        }

        public new void Dispose() { base.Cancel(); }
    }
    public class PCLTimer
    {
        private Timer _timer;
        private TimeSpan _interval;

        /// <summary>
        /// Interval between signals in milliseconds.
        /// </summary>
        public double Interval
        {
            get { return _interval.TotalMilliseconds; }
            set { _interval = TimeSpan.FromMilliseconds(value); Stop(); Start(); }
        }

        /// <summary>
        /// True if PCLTimer is running, false if not.
        /// </summary>
        public bool Enabled
        {
            get { return null != _timer; }
            set { if (value) Start(); else Stop(); }
        }

        /// <summary>
        /// Occurs when the specified time has elapsed and the PCLTimer is enabled.
        /// </summary>
        public event EventHandler Elapsed;

        /// <summary>
        /// Starts the PCLTimer.
        /// </summary>
        public void Start()
        {
            if (0 == _interval.TotalMilliseconds)
                throw new InvalidOperationException("Set Elapsed property before calling PCLTimer.Start().");
            _timer = new Timer(OnElapsed, null, _interval, _interval);
        }

        /// <summary>
        /// Stops the PCLTimer.
        /// </summary>
        public void Stop()
        {
            _timer.Dispose();
        }

        /// <summary>
        /// Releases all resources.
        /// </summary>
        public void Dispose()
        {
            _timer.Dispose();
        }

        /// <summary>
        /// Invokes Elapsed event.
        /// </summary>
        /// <param name="state"></param>
        private void OnElapsed(object state)
        {
            if (null != _timer && null != Elapsed)
                Elapsed(this, EventArgs.Empty);
        }
    }
}

Solution

  • If you interested in implementing Timer with Tasks, you can try this code:

    public delegate void TimerCallback(object state);
    
    public sealed class Timer : CancellationTokenSource, IDisposable
    {
        public Timer(TimerCallback callback, object state, int dueTime, int period)
        {
            Task.Delay(dueTime, Token).ContinueWith(async (t, s) =>
            {
                var tuple = (Tuple<TimerCallback, object>) s;
    
                while (true)
                {
                    if (IsCancellationRequested)
                        break;
                    Task.Run(() => tuple.Item1(tuple.Item2));
                    await Task.Delay(period);
                }
    
            }, Tuple.Create(callback, state), CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
                TaskScheduler.Default);
        }
    
        public new void Dispose() { base.Cancel(); }
    }
    

    As already mentioned you can introduce dependency on some interface and ask user to give you particular timer implementation.