Beispiel #1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="AsyncTimer" /> class.
        /// </summary>
        /// <param name="callback">The asynchronous method to be executed.</param>
        /// <param name="period">The minimum gap between the start of the task invocation and the start of the previous task invocation (defautls to <see langword="null" /> which is equivalent to <see cref="TimeHelpers.InfiniteDuration" />).</param>
        /// <param name="dueTime">The due time between the last time the timeouts were changed and the start of the task invocation (defautls to <see langword="null" /> which is equivalent to <see cref="Duration.Zero" />).</param>
        /// <param name="minimumGap">The minimum gap between the start of the task invocation and the end of the previous task invocation (defautls to <see langword="null" /> which is equivalent to <see cref="Duration.Zero" />).</param>
        /// <param name="pauseToken">The pause token for pasuing the timer.</param>
        /// <param name="errorHandler">The optional error handler.</param>
        public AsyncTimer(
            [NotNull] AsyncTimerCallback callback,
            Duration?period                 = null,
            Duration?dueTime                = null,
            Duration?minimumGap             = null,
            PauseToken pauseToken           = default(PauseToken),
            Action <Exception> errorHandler = null)
        {
            if (callback == null)
            {
                throw new ArgumentNullException(nameof(callback));
            }

            long timeStamp = HighPrecisionClock.Instance.NowTicks;

            _callback   = callback;
            _pauseToken = pauseToken;
            _timeOuts   = new TimeOuts(
                period ?? TimeHelpers.InfiniteDuration,
                dueTime ?? Duration.Zero,
                minimumGap ?? Duration.Zero,
                timeStamp);

            _cancellationTokenSource  = new CancellationTokenSource();
            _timeOutsChanged          = new CancellationTokenSource();
            _callbackCompletionSource = null;

            _errorHandler = errorHandler;

            Task.Run(() => TimerTask(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
        }
Beispiel #2
0
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            CancellationTokenSource cts = Interlocked.Exchange(ref _cancellationTokenSource, null);

            if (!ReferenceEquals(cts, null))
            {
                cts.Cancel();
                cts.Dispose();
            }

            CancellationTokenSource pdc = Interlocked.Exchange(ref _timeOutsChanged, null);

            if (!ReferenceEquals(pdc, null))
            {
                pdc.Cancel();
                pdc.Dispose();
            }

            TaskCompletionSource <bool> tcs = Interlocked.Exchange(ref _callbackCompletionSource, null);

            if (!ReferenceEquals(tcs, null))
            {
                tcs.TrySetCanceled();
            }

            _timeOuts = null;
        }
Beispiel #3
0
        /// <summary>
        /// Changes the specified due time and period.
        /// </summary>
        /// <param name="period">The optional minimum gap between the start of the task invocation and the start of the previous task invocation; use <see langword="null"/> to leave the value unchanged.</param>
        /// <param name="dueTime">The optional due time between the last time the timeouts were changed and the start of the task invocation; use <see langword="null"/> to leave the value unchanged.</param>
        /// <param name="minimumGap">The optional minimum gap between the start of the task invocation and the end of the previous task invocation; use <see langword="null"/> to leave the value unchanged.</param>
        public void Change(Duration?period = null, Duration?dueTime = null, Duration?minimumGap = null)
        {
            long timeStamp         = HighPrecisionClock.Instance.NowTicks;
            bool dueTimeChanged    = !ReferenceEquals(dueTime, null);
            bool minimumGapChanged = !ReferenceEquals(minimumGap, null);
            bool periodChanged     = !ReferenceEquals(period, null);

            if (dueTimeChanged &&
                minimumGapChanged &&
                periodChanged)
            {
                // Changing everything so we can just go ahead and change

                CancellationTokenSource timeOutsChanged = _timeOutsChanged;
                // If we don't have a cancellation token we're disposed
                if (ReferenceEquals(timeOutsChanged, null))
                {
                    throw new ObjectDisposedException("AsyncTimer");
                }

                // Update the timeOuts and cancel timeOutsChanged, as timeOuts includes a timestamp it always changes.
                _timeOuts = new TimeOuts(period.Value, dueTime.Value, minimumGap.Value, timeStamp);
                timeOutsChanged.Cancel();
            }
            else if (dueTimeChanged ||
                     minimumGapChanged ||
                     periodChanged)
            {
                TimeOuts newTimeOuts, oldTimeOuts;
                // We're changing at least one thing
                do
                {
                    oldTimeOuts = _timeOuts;
                    // Check we have timeouts (might be disposed)
                    if (ReferenceEquals(oldTimeOuts, null))
                    {
                        return;
                    }

                    // If the current timestamp is newer than this ignore
                    if (oldTimeOuts.TimeStamp >= timeStamp)
                    {
                        return;
                    }

                    newTimeOuts = new TimeOuts(
                        period ?? oldTimeOuts.Period,
                        dueTime ?? oldTimeOuts.DueTime,
                        minimumGap ?? oldTimeOuts.MinimumGap,
                        timeStamp,
                        dueTime.HasValue ? (long?)null : oldTimeOuts.DueTimeStamp);
                } while (Interlocked.CompareExchange(ref _timeOuts, newTimeOuts, oldTimeOuts) != oldTimeOuts);

                CancellationTokenSource timeOutsChanged = _timeOutsChanged;
                // If we don't have a cancellation token we're disposed
                if (ReferenceEquals(timeOutsChanged, null))
                {
                    throw new ObjectDisposedException("AsyncTimer");
                }

                timeOutsChanged.Cancel();
            }
        }
Beispiel #4
0
        /// <summary>
        /// The timer task executes the callback asynchronously after set delays.
        /// </summary>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns></returns>
        // ReSharper disable once FunctionComplexityOverflow
        private async Task TimerTask(CancellationToken cancellationToken)
        {
            long startTicks = long.MinValue;
            long endTicks   = long.MinValue;

            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    CancellationTokenSource timeoutsChanged;

                    // Check we're not set to run immediately
                    if (Interlocked.Exchange(ref _runImmediate, 0) == 0)
                    {
                        do
                        {
                            // Create new cancellation token source and set _timeOutsChanged to it in a thread-safe none-locking way.
                            timeoutsChanged = new CancellationTokenSource();
                            CancellationTokenSource toc = Interlocked.Exchange(ref _timeOutsChanged, timeoutsChanged);
                            if (ReferenceEquals(toc, null))
                            {
                                toc = Interlocked.CompareExchange(ref _timeOutsChanged, null, timeoutsChanged);
                                if (!ReferenceEquals(toc, null))
                                {
                                    toc.Dispose();
                                }
                                return;
                            }

                            // If we have run immediate set at this point, we can't rely on the correct _timeOutsChanged cts being cancelled.
                            if (Interlocked.Exchange(ref _runImmediate, 0) > 0)
                            {
                                break;
                            }

                            using (ITokenSource tokenSource = cancellationToken.CreateLinked(timeoutsChanged.Token))
                            {
                                // Check for pausing.
                                try
                                {
                                    await _pauseToken.WaitWhilePausedAsync(tokenSource.Token).ConfigureAwait(false);
                                }
                                catch (OperationCanceledException)
                                {
                                }
                                catch (Exception exception)
                                {
                                    if (!ReferenceEquals(_errorHandler, null))
                                    {
                                        _errorHandler(exception);
                                    }
                                }

                                if (cancellationToken.IsCancellationRequested)
                                {
                                    return;
                                }

                                // Get timeouts
                                TimeOuts timeOuts = _timeOuts;
                                if (ReferenceEquals(timeOuts, null))
                                {
                                    return;
                                }

                                if (timeOuts.DueTimeMs < 0 ||
                                    (startTicks > timeOuts.DueTimeStamp && (timeOuts.MinimumGapMs < 0 || timeOuts.PeriodMs < 0)))
                                {
                                    // If we have infinite waits then we are effectively awaiting cancellation
                                    // ReSharper disable once PossibleNullReferenceException
                                    await tokenSource.ConfigureAwait(false);

                                    if (cancellationToken.IsCancellationRequested)
                                    {
                                        return;
                                    }
                                    continue;
                                }

                                // If all timeouts are zero we effectively run again immediately (after checking we didn't get a cancellation
                                // indicating the value have changed again).
                                if (timeOuts.DueTimeMs == 0 &&
                                    timeOuts.MinimumGapMs == 0 &&
                                    timeOuts.PeriodMs == 0)
                                {
                                    continue;
                                }

                                int wait;

                                if (startTicks > long.MinValue)
                                {
                                    // Calculate the wait time based on the minimum gap and the period.
                                    long now = HighPrecisionClock.Instance.NowTicks;
                                    int  a   = timeOuts.PeriodMs -
                                               (int)((now - startTicks) / NodaConstants.TicksPerMillisecond);
                                    int b = timeOuts.MinimumGapMs -
                                            (int)((now - endTicks) / NodaConstants.TicksPerMillisecond);
                                    int c = (int)((timeOuts.DueTimeStamp - now) / NodaConstants.TicksPerMillisecond);

                                    wait = Math.Max(a, Math.Max(b, c));
                                }
                                else
                                {
                                    // Wait the initial due time
                                    wait =
                                        (int)
                                        ((timeOuts.DueTimeStamp - HighPrecisionClock.Instance.NowTicks) /
                                         NodaConstants.TicksPerMillisecond);
                                }

                                // If we don't need to wait run again immediately (after checking values haven't changed).
                                if (wait < 1)
                                {
                                    continue;
                                }

                                try
                                {
                                    // Wait for set milliseconds
                                    // ReSharper disable PossibleNullReferenceException
                                    await Task.Delay(wait, tokenSource.Token).ConfigureAwait(false);

                                    // ReSharper restore PossibleNullReferenceException
                                }
                                catch (OperationCanceledException)
                                {
                                }
                                catch (Exception exception)
                                {
                                    if (!ReferenceEquals(_errorHandler, null))
                                    {
                                        _errorHandler(exception);
                                    }
                                }
                            }

                            // Recalculate wait time if 'cancelled' due to signal, and not set to run immediately; or if we're currently paused.
                        } while (
                            _pauseToken.IsPaused ||
                            (timeoutsChanged.IsCancellationRequested &&
                             !cancellationToken.IsCancellationRequested &&
                             Interlocked.Exchange(ref _runImmediate, 0) < 1));
                    }

                    if (cancellationToken.IsCancellationRequested)
                    {
                        return;
                    }

                    try
                    {
                        Interlocked.CompareExchange(
                            ref _callbackCompletionSource,
                            new TaskCompletionSource <bool>(),
                            null);

                        startTicks = HighPrecisionClock.Instance.NowTicks;

                        // ReSharper disable once PossibleNullReferenceException
                        await _callback(cancellationToken).ConfigureAwait(false);

                        if (cancellationToken.IsCancellationRequested)
                        {
                            return;
                        }
                    }
                    catch (OperationCanceledException)
                    {
                        // Just finish as we're cancelled

                        TaskCompletionSource <bool> callbackCompletionSource =
                            Interlocked.Exchange(ref _callbackCompletionSource, null);

                        // If the completion source is not null, then someone is awaiting last execution, so complete the task
                        if (!ReferenceEquals(callbackCompletionSource, null))
                        {
                            callbackCompletionSource.TrySetCanceled();
                        }

                        return;
                    }
                    // ReSharper disable once EmptyGeneralCatchClause
                    catch (Exception exception)
                    {
                        // Supress errors thrown by callback, unless someone is awaiting it.

                        TaskCompletionSource <bool> callbackCompletionSource =
                            Interlocked.Exchange(ref _callbackCompletionSource, null);

                        // If the completion source is not null, then someone is awaiting last execution, so complete the task
                        if (!ReferenceEquals(callbackCompletionSource, null))
                        {
                            callbackCompletionSource.TrySetException(exception);
                        }

                        if (!ReferenceEquals(_errorHandler, null))
                        {
                            _errorHandler(exception);
                        }
                    }
                    finally
                    {
                        endTicks = HighPrecisionClock.Instance.NowTicks;

                        // If run immediately was set whilst we were running, we can clear it.
                        Interlocked.Exchange(ref _runImmediate, 0);

                        TaskCompletionSource <bool> callbackCompletionSource =
                            Interlocked.Exchange(ref _callbackCompletionSource, null);

                        // If the completion source is not null, then someone is awaiting last execution, so complete the task
                        if (!ReferenceEquals(callbackCompletionSource, null))
                        {
                            callbackCompletionSource.TrySetResult(true);
                        }
                    }
                }
                catch (Exception exception)
                {
                    if (!ReferenceEquals(_errorHandler, null))
                    {
                        _errorHandler(exception);
                    }
                }
            }
        }
Beispiel #5
0
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            CancellationTokenSource cts = Interlocked.Exchange(ref _cancellationTokenSource, null);
            if (!ReferenceEquals(cts, null))
            {
                cts.Cancel();
                cts.Dispose();
            }

            CancellationTokenSource pdc = Interlocked.Exchange(ref _timeOutsChanged, null);
            if (!ReferenceEquals(pdc, null))
            {
                pdc.Cancel();
                pdc.Dispose();
            }

            TaskCompletionSource<bool> tcs = Interlocked.Exchange(ref _callbackCompletionSource, null);
            if (!ReferenceEquals(tcs, null))
                tcs.TrySetCanceled();

            _timeOuts = null;
        }
Beispiel #6
0
        /// <summary>
        /// Changes the specified due time and period.
        /// </summary>
        /// <param name="period">The optional minimum gap between the start of the task invocation and the start of the previous task invocation; use <see langword="null"/> to leave the value unchanged.</param>
        /// <param name="dueTime">The optional due time between the last time the timeouts were changed and the start of the task invocation; use <see langword="null"/> to leave the value unchanged.</param>
        /// <param name="minimumGap">The optional minimum gap between the start of the task invocation and the end of the previous task invocation; use <see langword="null"/> to leave the value unchanged.</param>
        public void Change(Duration? period = null, Duration? dueTime = null, Duration? minimumGap = null)
        {
            long timeStamp = HighPrecisionClock.Instance.NowTicks;
            bool dueTimeChanged = !ReferenceEquals(dueTime, null);
            bool minimumGapChanged = !ReferenceEquals(minimumGap, null);
            bool periodChanged = !ReferenceEquals(period, null);
            if (dueTimeChanged &&
                minimumGapChanged &&
                periodChanged)
            {
                // Changing everything so we can just go ahead and change

                CancellationTokenSource timeOutsChanged = _timeOutsChanged;
                // If we don't have a cancellation token we're disposed
                if (ReferenceEquals(timeOutsChanged, null))
                    throw new ObjectDisposedException("AsyncTimer");

                // Update the timeOuts and cancel timeOutsChanged, as timeOuts includes a timestamp it always changes.
                _timeOuts = new TimeOuts(period.Value, dueTime.Value, minimumGap.Value, timeStamp);
                timeOutsChanged.Cancel();
            }
            else if (dueTimeChanged ||
                     minimumGapChanged ||
                     periodChanged)
            {
                TimeOuts newTimeOuts, oldTimeOuts;
                // We're changing at least one thing
                do
                {
                    oldTimeOuts = _timeOuts;
                    // Check we have timeouts (might be disposed)
                    if (ReferenceEquals(oldTimeOuts, null)) return;

                    // If the current timestamp is newer than this ignore
                    if (oldTimeOuts.TimeStamp >= timeStamp) return;

                    newTimeOuts = new TimeOuts(
                        period ?? oldTimeOuts.Period,
                        dueTime ?? oldTimeOuts.DueTime,
                        minimumGap ?? oldTimeOuts.MinimumGap,
                        timeStamp,
                        dueTime.HasValue ? (long?)null : oldTimeOuts.DueTimeStamp);
                } while (Interlocked.CompareExchange(ref _timeOuts, newTimeOuts, oldTimeOuts) != oldTimeOuts);

                CancellationTokenSource timeOutsChanged = _timeOutsChanged;
                // If we don't have a cancellation token we're disposed
                if (ReferenceEquals(timeOutsChanged, null))
                    throw new ObjectDisposedException("AsyncTimer");

                timeOutsChanged.Cancel();
            }
        }
Beispiel #7
0
        /// <summary>
        /// Initializes a new instance of the <see cref="AsyncTimer" /> class.
        /// </summary>
        /// <param name="callback">The asynchronous method to be executed.</param>
        /// <param name="period">The minimum gap between the start of the task invocation and the start of the previous task invocation (defautls to <see langword="null" /> which is equivalent to <see cref="TimeHelpers.InfiniteDuration" />).</param>
        /// <param name="dueTime">The due time between the last time the timeouts were changed and the start of the task invocation (defautls to <see langword="null" /> which is equivalent to <see cref="Duration.Zero" />).</param>
        /// <param name="minimumGap">The minimum gap between the start of the task invocation and the end of the previous task invocation (defautls to <see langword="null" /> which is equivalent to <see cref="Duration.Zero" />).</param>
        /// <param name="pauseToken">The pause token for pasuing the timer.</param>
        /// <param name="errorHandler">The optional error handler.</param>
        public AsyncTimer(
            [NotNull] AsyncTimerCallback callback,
            Duration? period = null,
            Duration? dueTime = null,
            Duration? minimumGap = null,
            PauseToken pauseToken = default(PauseToken),
            Action<Exception> errorHandler = null)
        {
            if (callback == null) throw new ArgumentNullException(nameof(callback));

            long timeStamp = HighPrecisionClock.Instance.NowTicks;
            _callback = callback;
            _pauseToken = pauseToken;
            _timeOuts = new TimeOuts(
                period ?? TimeHelpers.InfiniteDuration,
                dueTime ?? Duration.Zero,
                minimumGap ?? Duration.Zero,
                timeStamp);

            _cancellationTokenSource = new CancellationTokenSource();
            _timeOutsChanged = new CancellationTokenSource();
            _callbackCompletionSource = null;

            _errorHandler = errorHandler;

            Task.Run(() => TimerTask(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
        }