C# class to encapsulate and run cancelable action in the background.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Threading
{
    public class PeriodicAction : IDisposable
    {
        private readonly AutoResetEvent _updateAsap = new AutoResetEvent(false);
        private readonly AutoResetEvent _intervalChanged = new AutoResetEvent(false);
        private readonly Action<CancellationToken> _action;
        private CancellationTokenSource _ownCancellationSource;
        private CancellationTokenSource _cancellationSource;
        private Task _task;
        private TimeSpan _interval;

        public PeriodicAction(Action<CancellationToken> action, CancellationTokenSource cancellationTokenSource = null)
        {
            _action = action;
            _ownCancellationSource = new CancellationTokenSource();
            _cancellationSource = cancellationTokenSource;
            _task = Task.Factory.StartNew(Run, _ownCancellationSource.Token);
            _interval = TimeSpan.FromMinutes(1.0);
        }

        public void Dispose()
        {
            if (_task == null)
            {
                return;
            }

            _ownCancellationSource?.Cancel();
            try
            {
                _task.Wait();
            }
            catch (AggregateException ex)
            {
                if (ex.InnerExceptions.Count <= 1 && ex.InnerException is TaskCanceledException)
                {
                    return;
                }

                throw;
            }
            finally
            {
                _task = null;
                if (_ownCancellationSource != null)
                {
                    _ownCancellationSource.Dispose();
                    _ownCancellationSource = null;
                }
                _cancellationSource = null;
            }
        }

        public TimeSpan Interval
        {
            get { return _interval; }
            set
            {
                if (_interval == value)
                {
                    return;
                }

                _interval = value;
                _intervalChanged.Set();
            }
        }

        public bool IsEnabled { get; set; }

        public void PerformAsap()
        {
            _updateAsap.Set();
        }

        private void Run()
        {
            try
            {
                var waitHandles = new WaitHandle[3]
                {
                    _ownCancellationSource.Token.WaitHandle,
                    _intervalChanged,
                    _updateAsap
                };

                if (_cancellationSource != null)
                {
                    waitHandles = waitHandles.Concat(
                        new WaitHandle[1]
                        {
                            _cancellationSource.Token.WaitHandle
                        }).ToArray();
                }

                int index;
                while ((index = WaitHandle.WaitAny(waitHandles, Interval)) == 258 ||
                       waitHandles[index] == _intervalChanged || waitHandles[index] == _updateAsap)
                {
                    if ((index == 258 || waitHandles[index] != _intervalChanged) && IsEnabled)
                    {
                        _action((_cancellationSource ?? _ownCancellationSource).Token);
                    }
                }
            }
            catch
            {
                // eat error  ...
            }
        }
    }
}