/// <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); } } } }