Skip to content

Commit

Permalink
Replace Timer Factory with Timer.
Browse files Browse the repository at this point in the history
  • Loading branch information
tosh-coding committed Jul 10, 2024
1 parent fd561f3 commit d3ebe52
Show file tree
Hide file tree
Showing 22 changed files with 818 additions and 378 deletions.
149 changes: 149 additions & 0 deletions src/AsyncFiberWorks.Windows/Timer/IntervalWaitableTimerEx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using AsyncFiberWorks.Core;
using AsyncFiberWorks.Fibers;
using System;
using System.Threading;

namespace AsyncFiberWorks.Windows.Timer
{
/// <summary>
/// Timer using WaitableTimerEx in Windows.
/// This timer starts a dedicated thread.
/// </summary>
public class IntervalWaitableTimerEx : IIntervalTimer, IDisposable
{
readonly ThreadFiber _thread;
readonly WaitableTimerEx _waitableTimer;
readonly ManualResetEventSlim _resetEvent = new ManualResetEventSlim();
WaitHandle[] _waitHandles = null;
int _scheduled = 0;
bool _disposed = false;

object _lockObj { get { return _waitableTimer; } }

/// <summary>
/// Create a timer.
/// </summary>
public IntervalWaitableTimerEx()
{
_thread = new ThreadFiber();
_waitableTimer = new WaitableTimerEx(manualReset: false);
}

/// <summary>
/// Start a repeating timer.
/// </summary>
/// <param name="action">The process to be called when the timer expires.</param>
/// <param name="firstIntervalMs">Initial wait time. Must be greater than or equal to 0.</param>
/// <param name="intervalMs">The waiting interval time after the second time. Must be greater than 0.</param>
/// <param name="token">A handle to cancel the timer.</param>
public void ScheduleOnInterval(Action action, int firstIntervalMs, int intervalMs, CancellationToken token = default)
{
if (firstIntervalMs < 0)
{
throw new ArgumentOutOfRangeException(nameof(firstIntervalMs), $"{nameof(firstIntervalMs)} must be greater than or equal to 0.");
}
if (intervalMs <= 0)
{
throw new ArgumentOutOfRangeException(nameof(intervalMs), $"{nameof(intervalMs)} must be greater than 0.");
}

var copiedAction = action;
lock (_lockObj)
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
if (_scheduled > 0)
{
_resetEvent.Set();
}
_scheduled += 1;

_thread.Enqueue(() =>
{
lock (_lockObj)
{
if (_scheduled > 1)
{
_scheduled -= 1;
return;
}
if (_disposed)
{
return;
}
_resetEvent.Reset();
SetWaitHandles(token);
}

_waitableTimer.Set(firstIntervalMs * -10000L, intervalMs);
int index = WaitHandle.WaitAny(_waitHandles);
if (index == 0)
{
copiedAction();
}
else
{
_waitableTimer.Cancel();
}

lock (_lockObj)
{
_scheduled -= 1;
}
});
}
}

private void SetWaitHandles(CancellationToken externalToken)
{
if (externalToken.CanBeCanceled)
{
const int needSize = 3;
if ((_waitHandles?.Length ?? 0) != needSize)
{
_waitHandles = new WaitHandle[needSize];
}
_waitHandles[0] = _waitableTimer;
_waitHandles[1] = _resetEvent.WaitHandle;
_waitHandles[2] = externalToken.WaitHandle;
}
else
{
const int needSize = 2;
if ((_waitHandles?.Length ?? 0) != needSize)
{
_waitHandles = new WaitHandle[needSize];
}
_waitHandles[0] = _waitableTimer;
_waitHandles[1] = _resetEvent.WaitHandle;
}
}

void DisposeResources()
{
_waitableTimer.Dispose();
_resetEvent.Dispose();
_thread.Dispose();
_waitHandles = null;
}

/// <summary>
/// Stop the timer.
/// </summary>
public void Dispose()
{
lock (_lockObj)
{
if (_disposed)
{
return;
}
_disposed = true;
_resetEvent.Set();
_thread.Enqueue(DisposeResources);
}
}
}
}
144 changes: 144 additions & 0 deletions src/AsyncFiberWorks.Windows/Timer/OneshotWaitableTimerEx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using AsyncFiberWorks.Core;
using AsyncFiberWorks.Fibers;
using System;
using System.Threading;

namespace AsyncFiberWorks.Windows.Timer
{
/// <summary>
/// Timer using WaitableTimerEx in Windows.
/// This timer starts a dedicated thread.
/// </summary>
public class OneshotWaitableTimerEx : IOneshotTimer, IDisposable
{
readonly ThreadFiber _thread;
readonly WaitableTimerEx _waitableTimer;
readonly ManualResetEventSlim _resetEvent = new ManualResetEventSlim();
WaitHandle[] _waitHandles = null;
int _scheduled = 0;
bool _disposed = false;

object _lockObj { get { return _waitableTimer; } }

/// <summary>
/// Create a timer.
/// </summary>
public OneshotWaitableTimerEx()
{
_thread = new ThreadFiber();
_waitableTimer = new WaitableTimerEx(manualReset: false);
}

/// <summary>
/// Start a timer.
/// </summary>
/// <param name="action">The process to be called when the timer expires.</param>
/// <param name="firstIntervalMs">Timer wait time. Must be greater than or equal to 0.</param>
/// <param name="token">A handle to cancel the timer.</param>
public void Schedule(Action action, int firstIntervalMs, CancellationToken token = default)
{
if (firstIntervalMs < 0)
{
throw new ArgumentOutOfRangeException(nameof(firstIntervalMs), $"{nameof(firstIntervalMs)} must be greater than or equal to 0.");
}

var copiedAction = action;
lock (_lockObj)
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
if (_scheduled > 0)
{
_resetEvent.Set();
}
_scheduled += 1;

_thread.Enqueue(() =>
{
lock (_lockObj)
{
if (_scheduled > 1)
{
_scheduled -= 1;
return;
}
if (_disposed)
{
return;
}
_resetEvent.Reset();
SetWaitHandles(token);
}

_waitableTimer.Set(firstIntervalMs * -10000L);
int index = WaitHandle.WaitAny(_waitHandles);
if (index == 0)
{
copiedAction();
}
else
{
_waitableTimer.Cancel();
}

lock (_lockObj)
{
_scheduled -= 1;
}
});
}
}

private void SetWaitHandles(CancellationToken externalToken)
{
if (externalToken.CanBeCanceled)
{
const int needSize = 3;
if ((_waitHandles?.Length ?? 0) != needSize)
{
_waitHandles = new WaitHandle[needSize];
}
_waitHandles[0] = _waitableTimer;
_waitHandles[1] = _resetEvent.WaitHandle;
_waitHandles[2] = externalToken.WaitHandle;
}
else
{
const int needSize = 2;
if ((_waitHandles?.Length ?? 0) != needSize)
{
_waitHandles = new WaitHandle[needSize];
}
_waitHandles[0] = _waitableTimer;
_waitHandles[1] = _resetEvent.WaitHandle;
}
}

void DisposeResources()
{
_waitableTimer.Dispose();
_resetEvent.Dispose();
_thread.Dispose();
_waitHandles = null;
}

/// <summary>
/// Stop the timer.
/// </summary>
public void Dispose()
{
lock (_lockObj)
{
if (_disposed)
{
return;
}
_disposed = true;
_resetEvent.Set();
_thread.Enqueue(DisposeResources);
}
}
}
}
60 changes: 0 additions & 60 deletions src/AsyncFiberWorks.Windows/Timer/WaitableTimerExFactory.cs

This file was deleted.

20 changes: 20 additions & 0 deletions src/AsyncFiberWorks/Core/IIntervalTimer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Threading;

namespace AsyncFiberWorks.Core
{
/// <summary>
/// Repeating timer.
/// </summary>
public interface IIntervalTimer : IDisposable
{
/// <summary>
/// Start a repeating timer.
/// </summary>
/// <param name="action">The process to be called when the timer expires.</param>
/// <param name="firstIntervalMs">Initial wait time. Must be greater than or equal to 0.</param>
/// <param name="intervalMs">The waiting interval time after the second time. Must be greater than 0.</param>
/// <param name="cancellation">A handle to cancel the timer.</param>
void ScheduleOnInterval(Action action, int firstIntervalMs, int intervalMs, CancellationToken cancellation = default);
}
}
19 changes: 0 additions & 19 deletions src/AsyncFiberWorks/Core/IIntervalTimerFactory.cs

This file was deleted.

Loading

0 comments on commit d3ebe52

Please sign in to comment.