src/Serilog.Sinks.AzureDataExplorer/Durable/PortableTimer.cs (100 lines of code) (raw):
// Copyright 2014 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Serilog.Debugging;
namespace Serilog.Sinks.AzureDataExplorer.Durable
{
/// <summary>
/// https://github.com/serilog/serilog-sinks-seq/blob/v4.0.0/src/Serilog.Sinks.Seq/Sinks/Seq/PortableTimer.cs
/// The PortableTimer class is a timer implementation that executes a specified function as a recurring task after a specified time interval.
/// The timer is implemented as a combination of the System.Threading.Timer class and the Task.Delay method, depending on the THREADING_TIMER constant.
/// It implements the IDisposable interface, which allows the timer to be cleaned up when it's no longer needed.
/// The timer is started using the Start method, which accepts a TimeSpan argument specifying the interval at which the task should be executed.
/// The task is executed in an asynchronous manner using the async keyword.
/// The OnTick method is protected by a lock to ensure that only one instance of the task is running at a time, and it checks for cancellation before executing the task.
/// The Dispose method cancels the timer and releases any resources associated with it.
/// </summary>
class PortableTimer : IDisposable
{
readonly object m_stateLock = new object();
readonly Func<CancellationToken, Task> m_onTick;
readonly CancellationTokenSource m_cancel = new CancellationTokenSource();
#if THREADING_TIMER
readonly Timer _timer;
#endif
bool m_running;
bool m_disposed;
public PortableTimer(Func<CancellationToken, Task> onTick)
{
if (onTick == null) throw new ArgumentNullException(nameof(onTick));
m_onTick = onTick;
#if THREADING_TIMER
_timer = new Timer(_ => OnTick(), null, Timeout.Infinite, Timeout.Infinite);
#endif
}
public void Start(TimeSpan interval)
{
if (interval < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(interval));
lock (m_stateLock)
{
if (m_disposed)
throw new ObjectDisposedException(nameof(PortableTimer));
#if THREADING_TIMER
_timer.Change(interval, Timeout.InfiniteTimeSpan);
#else
Task.Delay(interval, m_cancel.Token)
.ContinueWith(
_ => OnTick(),
CancellationToken.None,
TaskContinuationOptions.DenyChildAttach,
TaskScheduler.Default);
#endif
}
}
async void OnTick()
{
try
{
lock (m_stateLock)
{
if (m_disposed)
{
return;
}
// There's a little bit of raciness here, but it's needed to support the
// current API, which allows the tick handler to reenter and set the next interval.
if (m_running)
{
Monitor.Wait(m_stateLock);
if (m_disposed)
{
return;
}
}
m_running = true;
}
if (!m_cancel.Token.IsCancellationRequested)
{
await m_onTick(m_cancel.Token);
}
}
catch (OperationCanceledException tcx)
{
SelfLog.WriteLine("The timer was canceled during invocation: {0}", tcx);
}
finally
{
lock (m_stateLock)
{
m_running = false;
Monitor.PulseAll(m_stateLock);
}
}
}
public void Dispose()
{
m_cancel.Cancel();
lock (m_stateLock)
{
if (m_disposed)
{
return;
}
while (m_running)
{
Monitor.Wait(m_stateLock);
}
#if THREADING_TIMER
_timer.Dispose();
#endif
m_disposed = true;
}
}
}
}