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