Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data

This commit is contained in:
2026-01-14 22:11:11 +01:00
parent 40a8431464
commit 3f7122d30a
350 changed files with 41444 additions and 119 deletions

View File

@@ -0,0 +1,24 @@
namespace Swan.Threading
{
/// <summary>
/// Fast, atomic boolean combining interlocked to write value and volatile to read values.
/// </summary>
public sealed class AtomicBoolean : AtomicTypeBase<bool>
{
/// <summary>
/// Initializes a new instance of the <see cref="AtomicBoolean"/> class.
/// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param>
public AtomicBoolean(bool initialValue = default)
: base(initialValue ? 1 : 0)
{
// placeholder
}
/// <inheritdoc/>
protected override bool FromLong(long backingValue) => backingValue != 0;
/// <inheritdoc/>
protected override long ToLong(bool value) => value ? 1 : 0;
}
}

View File

@@ -0,0 +1,26 @@
using System;
namespace Swan.Threading
{
/// <summary>
/// Defines an atomic DateTime.
/// </summary>
public sealed class AtomicDateTime : AtomicTypeBase<DateTime>
{
/// <summary>
/// Initializes a new instance of the <see cref="AtomicDateTime"/> class.
/// </summary>
/// <param name="initialValue">The initial value.</param>
public AtomicDateTime(DateTime initialValue)
: base(initialValue.Ticks)
{
// placeholder
}
/// <inheritdoc />
protected override DateTime FromLong(long backingValue) => new DateTime(backingValue);
/// <inheritdoc />
protected override long ToLong(DateTime value) => value.Ticks;
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace Swan.Threading
{
/// <summary>
/// Fast, atomic double combining interlocked to write value and volatile to read values.
/// </summary>
public sealed class AtomicDouble : AtomicTypeBase<double>
{
/// <summary>
/// Initializes a new instance of the <see cref="AtomicDouble"/> class.
/// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param>
public AtomicDouble(double initialValue = default)
: base(BitConverter.DoubleToInt64Bits(initialValue))
{
// placeholder
}
/// <inheritdoc/>
protected override double FromLong(long backingValue) =>
BitConverter.Int64BitsToDouble(backingValue);
/// <inheritdoc/>
protected override long ToLong(double value) =>
BitConverter.DoubleToInt64Bits(value);
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Threading;
namespace Swan.Threading
{
/// <summary>
/// Defines an atomic generic Enum.
/// </summary>
/// <typeparam name="T">The type of enum.</typeparam>
public sealed class AtomicEnum<T>
where T : struct, IConvertible
{
private long _backingValue;
/// <summary>
/// Initializes a new instance of the <see cref="AtomicEnum{T}"/> class.
/// </summary>
/// <param name="initialValue">The initial value.</param>
/// <exception cref="ArgumentException">T must be an enumerated type.</exception>
public AtomicEnum(T initialValue)
{
if (!Enum.IsDefined(typeof(T), initialValue))
throw new ArgumentException("T must be an enumerated type");
Value = initialValue;
}
/// <summary>
/// Gets or sets the value.
/// </summary>
public T Value
{
get => (T)Enum.ToObject(typeof(T), BackingValue);
set => BackingValue = Convert.ToInt64(value);
}
private long BackingValue
{
get => Interlocked.Read(ref _backingValue);
set => Interlocked.Exchange(ref _backingValue, value);
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace Swan.Threading
{
/// <summary>
/// Represents an atomically readable or writable integer.
/// </summary>
public class AtomicInteger : AtomicTypeBase<int>
{
/// <summary>
/// Initializes a new instance of the <see cref="AtomicInteger"/> class.
/// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param>
public AtomicInteger(int initialValue = default)
: base(Convert.ToInt64(initialValue))
{
// placeholder
}
/// <inheritdoc/>
protected override int FromLong(long backingValue) =>
Convert.ToInt32(backingValue);
/// <inheritdoc/>
protected override long ToLong(int value) =>
Convert.ToInt64(value);
}
}

View File

@@ -0,0 +1,24 @@
namespace Swan.Threading
{
/// <summary>
/// Fast, atomic long combining interlocked to write value and volatile to read values.
/// </summary>
public sealed class AtomicLong : AtomicTypeBase<long>
{
/// <summary>
/// Initializes a new instance of the <see cref="AtomicLong"/> class.
/// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param>
public AtomicLong(long initialValue = default)
: base(initialValue)
{
// placeholder
}
/// <inheritdoc />
protected override long FromLong(long backingValue) => backingValue;
/// <inheritdoc />
protected override long ToLong(long value) => value;
}
}

View File

@@ -0,0 +1,26 @@
using System;
namespace Swan.Threading
{
/// <summary>
/// Represents an atomic TimeSpan type.
/// </summary>
public sealed class AtomicTimeSpan : AtomicTypeBase<TimeSpan>
{
/// <summary>
/// Initializes a new instance of the <see cref="AtomicTimeSpan" /> class.
/// </summary>
/// <param name="initialValue">The initial value.</param>
public AtomicTimeSpan(TimeSpan initialValue)
: base(initialValue.Ticks)
{
// placeholder
}
/// <inheritdoc />
protected override TimeSpan FromLong(long backingValue) => TimeSpan.FromTicks(backingValue);
/// <inheritdoc />
protected override long ToLong(TimeSpan value) => value.Ticks < 0 ? 0 : value.Ticks;
}
}

View File

@@ -0,0 +1,243 @@
using System;
using System.Threading;
namespace Swan.Threading
{
/// <summary>
/// Provides a generic implementation of an Atomic (interlocked) type
///
/// Idea taken from Memory model and .NET operations in article:
/// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
/// </summary>
/// <typeparam name="T">The structure type backed by a 64-bit value.</typeparam>
public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>>
where T : struct, IComparable, IComparable<T>, IEquatable<T>
{
private long _backingValue;
/// <summary>
/// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class.
/// </summary>
/// <param name="initialValue">The initial value.</param>
protected AtomicTypeBase(long initialValue)
{
BackingValue = initialValue;
}
/// <summary>
/// Gets or sets the value.
/// </summary>
public T Value
{
get => FromLong(BackingValue);
set => BackingValue = ToLong(value);
}
/// <summary>
/// Gets or sets the backing value.
/// </summary>
protected long BackingValue
{
get => Interlocked.Read(ref _backingValue);
set => Interlocked.Exchange(ref _backingValue, value);
}
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
/// <summary>
/// Implements the operator &gt;.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
/// <summary>
/// Implements the operator &lt;.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
/// <summary>
/// Implements the operator &gt;=.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
/// <summary>
/// Implements the operator &lt;=.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
/// <summary>
/// Implements the operator ++.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance)
{
Interlocked.Increment(ref instance._backingValue);
return instance;
}
/// <summary>
/// Implements the operator --.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance)
{
Interlocked.Decrement(ref instance._backingValue);
return instance;
}
/// <summary>
/// Implements the operator -&lt;.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="operand">The operand.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, long operand)
{
instance.BackingValue = instance.BackingValue + operand;
return instance;
}
/// <summary>
/// Implements the operator -.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="operand">The operand.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, long operand)
{
instance.BackingValue = instance.BackingValue - operand;
return instance;
}
/// <summary>
/// Compares the value to the other instance.
/// </summary>
/// <param name="other">The other instance.</param>
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
/// <exception cref="ArgumentException">When types are incompatible.</exception>
public int CompareTo(object other)
{
switch (other)
{
case null:
return 1;
case AtomicTypeBase<T> atomic:
return BackingValue.CompareTo(atomic.BackingValue);
case T variable:
return Value.CompareTo(variable);
}
throw new ArgumentException("Incompatible comparison types");
}
/// <summary>
/// Compares the value to the other instance.
/// </summary>
/// <param name="other">The other instance.</param>
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
public int CompareTo(T other) => Value.CompareTo(other);
/// <summary>
/// Compares the value to the other instance.
/// </summary>
/// <param name="other">The other instance.</param>
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
public int CompareTo(AtomicTypeBase<T> other) => BackingValue.CompareTo(other?.BackingValue ?? default);
/// <summary>
/// Determines whether the specified <see cref="object" />, is equal to this instance.
/// </summary>
/// <param name="other">The <see cref="object" /> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object other)
{
switch (other)
{
case AtomicTypeBase<T> atomic:
return Equals(atomic);
case T variable:
return Equals(variable);
}
return false;
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode() => BackingValue.GetHashCode();
/// <inheritdoc />
public bool Equals(AtomicTypeBase<T> other) =>
BackingValue == (other?.BackingValue ?? default);
/// <inheritdoc />
public bool Equals(T other) => Equals(Value, other);
/// <summary>
/// Converts from a long value to the target type.
/// </summary>
/// <param name="backingValue">The backing value.</param>
/// <returns>The value converted form a long value.</returns>
protected abstract T FromLong(long backingValue);
/// <summary>
/// Converts from the target type to a long value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The value converted to a long value.</returns>
protected abstract long ToLong(T value);
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Threading;
namespace Swan.Threading
{
/// <summary>
/// Acts as a <see cref="CancellationTokenSource"/> but with reusable tokens.
/// </summary>
public sealed class CancellationTokenOwner : IDisposable
{
private readonly object _syncLock = new object();
private bool _isDisposed;
private CancellationTokenSource _tokenSource = new CancellationTokenSource();
/// <summary>
/// Gets the token of the current.
/// </summary>
public CancellationToken Token
{
get
{
lock (_syncLock)
{
return _isDisposed
? CancellationToken.None
: _tokenSource.Token;
}
}
}
/// <summary>
/// Cancels the last referenced token and creates a new token source.
/// </summary>
public void Cancel()
{
lock (_syncLock)
{
if (_isDisposed) return;
_tokenSource.Cancel();
_tokenSource.Dispose();
_tokenSource = new CancellationTokenSource();
}
}
/// <inheritdoc />
public void Dispose() => Dispose(true);
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool disposing)
{
lock (_syncLock)
{
if (_isDisposed) return;
if (disposing)
{
_tokenSource.Cancel();
_tokenSource.Dispose();
}
_isDisposed = true;
}
}
}
}

View File

@@ -0,0 +1,232 @@
using System;
using System.Threading;
namespace Swan.Threading
{
/// <summary>
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time
/// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out
/// on the same <see cref="ThreadPool"/> thread every time the timer fires.
/// </summary>
public sealed class ExclusiveTimer : IDisposable
{
private readonly object _syncLock = new object();
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true);
private readonly Timer _backingTimer;
private readonly TimerCallback _userCallback;
private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
private int _period;
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="state">The state.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(TimerCallback timerCallback, object? state, int dueTime, int period)
{
_period = period;
_userCallback = timerCallback;
_backingTimer = new Timer(InternalCallback, state ?? this, dueTime, Timeout.Infinite);
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="state">The state.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(TimerCallback timerCallback, object? state, TimeSpan dueTime, TimeSpan period)
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds))
{
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
public ExclusiveTimer(TimerCallback timerCallback)
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite)
{
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(Action timerCallback, int dueTime, int period)
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
{
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period)
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
{
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
public ExclusiveTimer(Action timerCallback)
: this(timerCallback, Timeout.Infinite, Timeout.Infinite)
{
// placeholder
}
/// <summary>
/// Gets a value indicating whether this instance is disposing.
/// </summary>
/// <value>
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
/// </value>
public bool IsDisposing => _isDisposing.Value;
/// <summary>
/// Gets a value indicating whether this instance is disposed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
/// </value>
public bool IsDisposed => _isDisposed.Value;
/// <summary>
/// Waits until the time is elapsed.
/// </summary>
/// <param name="untilDate">The until date.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public static void WaitUntil(DateTime untilDate, CancellationToken cancellationToken = default)
{
static void Callback(IWaitEvent waitEvent)
{
try
{
waitEvent.Complete();
waitEvent.Begin();
}
catch
{
// ignore
}
}
using var delayLock = WaitEventFactory.Create(true);
using var timer = new ExclusiveTimer(() => Callback(delayLock), 0, 15);
while (!cancellationToken.IsCancellationRequested && DateTime.UtcNow < untilDate)
delayLock.Wait();
}
/// <summary>
/// Waits the specified wait time.
/// </summary>
/// <param name="waitTime">The wait time.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public static void Wait(TimeSpan waitTime, CancellationToken cancellationToken = default) =>
WaitUntil(DateTime.UtcNow.Add(waitTime), cancellationToken);
/// <summary>
/// Changes the start time and the interval between method invocations for the internal timer.
/// </summary>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public void Change(int dueTime, int period)
{
_period = period;
_backingTimer.Change(dueTime, Timeout.Infinite);
}
/// <summary>
/// Changes the start time and the interval between method invocations for the internal timer.
/// </summary>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public void Change(TimeSpan dueTime, TimeSpan period)
=> Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
/// <summary>
/// Changes the interval between method invocations for the internal timer.
/// </summary>
/// <param name="period">The period.</param>
public void Resume(int period) => Change(0, period);
/// <summary>
/// Changes the interval between method invocations for the internal timer.
/// </summary>
/// <param name="period">The period.</param>
public void Resume(TimeSpan period) => Change(TimeSpan.Zero, period);
/// <summary>
/// Pauses this instance.
/// </summary>
public void Pause() => Change(Timeout.Infinite, Timeout.Infinite);
/// <inheritdoc />
public void Dispose()
{
lock (_syncLock)
{
if (_isDisposed == true || _isDisposing == true)
return;
_isDisposing.Value = true;
}
try
{
_cycleDoneEvent.Wait();
_cycleDoneEvent.Dispose();
Pause();
_backingTimer.Dispose();
}
finally
{
_isDisposed.Value = true;
_isDisposing.Value = false;
}
}
/// <summary>
/// Logic that runs every time the timer hits the due time.
/// </summary>
/// <param name="state">The state.</param>
private void InternalCallback(object state)
{
lock (_syncLock)
{
if (IsDisposed || IsDisposing)
return;
}
if (_cycleDoneEvent.IsSet == false)
return;
_cycleDoneEvent.Reset();
try
{
_userCallback(state);
}
finally
{
_cycleDoneEvent?.Set();
_backingTimer?.Change(_period, Timeout.Infinite);
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace Swan.Threading
{
/// <summary>
/// Defines a generic interface for synchronized locking mechanisms.
/// </summary>
public interface ISyncLocker : IDisposable
{
/// <summary>
/// Acquires a writer lock.
/// The lock is released when the returned locking object is disposed.
/// </summary>
/// <returns>A disposable locking object.</returns>
IDisposable AcquireWriterLock();
/// <summary>
/// Acquires a reader lock.
/// The lock is released when the returned locking object is disposed.
/// </summary>
/// <returns>A disposable locking object.</returns>
IDisposable AcquireReaderLock();
}
}

View File

@@ -0,0 +1,57 @@
using System;
namespace Swan.Threading
{
/// <summary>
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim.
/// </summary>
/// <seealso cref="IDisposable" />
public interface IWaitEvent : IDisposable
{
/// <summary>
/// Gets a value indicating whether the event is in the completed state.
/// </summary>
bool IsCompleted { get; }
/// <summary>
/// Gets a value indicating whether the Begin method has been called.
/// It returns false after the Complete method is called.
/// </summary>
bool IsInProgress { get; }
/// <summary>
/// Returns true if the underlying handle is not closed and it is still valid.
/// </summary>
bool IsValid { get; }
/// <summary>
/// Gets a value indicating whether this instance is disposed.
/// </summary>
bool IsDisposed { get; }
/// <summary>
/// Enters the state in which waiters need to wait.
/// All future waiters will block when they call the Wait method.
/// </summary>
void Begin();
/// <summary>
/// Leaves the state in which waiters need to wait.
/// All current waiters will continue.
/// </summary>
void Complete();
/// <summary>
/// Waits for the event to be completed.
/// </summary>
void Wait();
/// <summary>
/// Waits for the event to be completed.
/// Returns <c>true</c> when there was no timeout. False if the timeout was reached.
/// </summary>
/// <param name="timeout">The maximum amount of time to wait for.</param>
/// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns>
bool Wait(TimeSpan timeout);
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Threading.Tasks;
namespace Swan.Threading
{
/// <summary>
/// Defines a standard API to control background application workers.
/// </summary>
public interface IWorker
{
/// <summary>
/// Gets the current state of the worker.
/// </summary>
WorkerState WorkerState { get; }
/// <summary>
/// Gets a value indicating whether this instance is disposed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
/// </value>
bool IsDisposed { get; }
/// <summary>
/// Gets a value indicating whether this instance is currently being disposed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
/// </value>
bool IsDisposing { get; }
/// <summary>
/// Gets or sets the time interval used to execute cycles.
/// </summary>
TimeSpan Period { get; set; }
/// <summary>
/// Gets the name identifier of this worker.
/// </summary>
string Name { get; }
/// <summary>
/// Starts execution of worker cycles.
/// </summary>
/// <returns>The awaitable task.</returns>
Task<WorkerState> StartAsync();
/// <summary>
/// Pauses execution of worker cycles.
/// </summary>
/// <returns>The awaitable task.</returns>
Task<WorkerState> PauseAsync();
/// <summary>
/// Resumes execution of worker cycles.
/// </summary>
/// <returns>The awaitable task.</returns>
Task<WorkerState> ResumeAsync();
/// <summary>
/// Permanently stops execution of worker cycles.
/// An interrupt is always sent to the worker. If you wish to stop
/// the worker without interrupting then call the <see cref="PauseAsync"/>
/// method, await it, and finally call the <see cref="StopAsync"/> method.
/// </summary>
/// <returns>The awaitable task.</returns>
Task<WorkerState> StopAsync();
}
}

View File

@@ -0,0 +1,21 @@
using System.Threading;
using System.Threading.Tasks;
namespace Swan.Threading
{
/// <summary>
/// An interface for a worker cycle delay provider.
/// </summary>
public interface IWorkerDelayProvider
{
/// <summary>
/// Suspends execution queues a new cycle for execution. The delay is given in
/// milliseconds. When overridden in a derived class the wait handle will be set
/// whenever an interrupt is received.
/// </summary>
/// <param name="wantedDelay">The remaining delay to wait for in the cycle.</param>
/// <param name="delayTask">Contains a reference to a task with the scheduled period delay.</param>
/// <param name="token">The cancellation token to cancel waiting.</param>
void ExecuteCycleDelay(int wantedDelay, Task delayTask, CancellationToken token);
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Swan.Logging;
namespace Swan.Threading
{
/// <summary>
/// Schedule an action to be periodically executed on the thread pool.
/// </summary>
public sealed class PeriodicTask : IDisposable
{
/// <summary>
/// <para>The minimum interval between action invocations.</para>
/// <para>The value of this field is equal to 100 milliseconds.</para>
/// </summary>
public static readonly TimeSpan MinInterval = TimeSpan.FromMilliseconds(100);
private readonly Func<CancellationToken, Task> _action;
private readonly CancellationTokenSource _cancellationTokenSource;
private TimeSpan _interval;
/// <summary>
/// Initializes a new instance of the <see cref="PeriodicTask"/> class.
/// </summary>
/// <param name="interval">The interval between invocations of <paramref name="action"/>.</param>
/// <param name="action">The callback to invoke periodically.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel operations.</param>
public PeriodicTask(TimeSpan interval, Func<CancellationToken, Task> action, CancellationToken cancellationToken = default)
{
_action = action ?? throw new ArgumentNullException(nameof(action));
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_interval = ValidateInterval(interval);
Task.Run(ActionLoop);
}
/// <summary>
/// Finalizes an instance of the <see cref="PeriodicTask"/> class.
/// </summary>
~PeriodicTask()
{
Dispose(false);
}
/// <summary>
/// <para>Gets or sets the interval between periodic action invocations.</para>
/// <para>Changes to this property take effect after next action invocation.</para>
/// </summary>
/// <seealso cref="MinInterval"/>
public TimeSpan Interval
{
get => _interval;
set => _interval = ValidateInterval(value);
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
}
}
private static TimeSpan ValidateInterval(TimeSpan value)
=> value < MinInterval ? MinInterval : value;
private async Task ActionLoop()
{
for (; ; )
{
try
{
await Task.Delay(Interval, _cancellationTokenSource.Token).ConfigureAwait(false);
await _action(_cancellationTokenSource.Token).ConfigureAwait(false);
}
catch (OperationCanceledException) when (_cancellationTokenSource.IsCancellationRequested)
{
break;
}
catch (TaskCanceledException) when (_cancellationTokenSource.IsCancellationRequested)
{
break;
}
catch (Exception ex)
{
ex.Log(nameof(PeriodicTask));
}
}
}
}
}

View File

@@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Swan.Configuration;
using Swan.Logging;
namespace Swan.Threading
{
/// <summary>
/// Represents an background worker abstraction with a life cycle and running at a independent thread.
/// </summary>
public abstract class RunnerBase : ConfiguredObject, IDisposable
{
private Thread? _worker;
private CancellationTokenSource? _cancelTokenSource;
private ManualResetEvent? _workFinished;
/// <summary>
/// Initializes a new instance of the <see cref="RunnerBase"/> class.
/// </summary>
/// <param name="isEnabled">if set to <c>true</c> [is enabled].</param>
protected RunnerBase(bool isEnabled)
{
Name = GetType().Name;
IsEnabled = isEnabled;
}
/// <summary>
/// Gets the error messages.
/// </summary>
/// <value>
/// The error messages.
/// </value>
public List<string> ErrorMessages { get; } = new List<string>();
/// <summary>
/// Gets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; }
/// <summary>
/// Gets a value indicating whether this instance is running.
/// </summary>
/// <value>
/// <c>true</c> if this instance is running; otherwise, <c>false</c>.
/// </value>
public bool IsRunning { get; private set; }
/// <summary>
/// Gets a value indicating whether this instance is enabled.
/// </summary>
/// <value>
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
/// </value>
public bool IsEnabled { get; }
/// <summary>
/// Starts this instance.
/// </summary>
public virtual void Start()
{
if (IsEnabled == false)
return;
"Start Requested".Debug(Name);
_cancelTokenSource = new CancellationTokenSource();
_workFinished = new ManualResetEvent(false);
_worker = new Thread(() =>
{
_workFinished.Reset();
IsRunning = true;
try
{
Setup();
DoBackgroundWork(_cancelTokenSource.Token);
}
catch (ThreadAbortException)
{
$"{nameof(ThreadAbortException)} caught.".Warn(Name);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
$"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(Name);
}
finally
{
Cleanup();
_workFinished?.Set();
IsRunning = false;
"Stopped Completely".Debug(Name);
}
})
{
IsBackground = true,
Name = $"{Name}Thread",
};
_worker.Start();
}
/// <summary>
/// Stops this instance.
/// </summary>
public virtual void Stop()
{
if (IsEnabled == false || IsRunning == false)
return;
"Stop Requested".Debug(Name);
_cancelTokenSource?.Cancel();
var waitRetries = 5;
while (waitRetries >= 1)
{
if (_workFinished?.WaitOne(250) ?? true)
{
waitRetries = -1;
break;
}
waitRetries--;
}
if (waitRetries < 0)
{
"Workbench stopped gracefully".Debug(Name);
}
else
{
"Did not respond to stop request. Aborting thread and waiting . . .".Warn(Name);
_worker?.Abort();
if (_workFinished?.WaitOne(5000) == false)
"Waited and no response. Worker might have been left in an inconsistent state.".Error(Name);
else
"Waited for worker and it finally responded (OK).".Debug(Name);
}
_workFinished?.Dispose();
_workFinished = null;
}
/// <inheritdoc/>
public void Dispose()
{
_cancelTokenSource?.Dispose();
_workFinished?.Dispose();
}
/// <summary>
/// Setups this instance.
/// </summary>
protected void Setup()
{
EnsureConfigurationNotLocked();
OnSetup();
LockConfiguration();
}
/// <summary>
/// Cleanups this instance.
/// </summary>
protected virtual void Cleanup()
{
// empty
}
/// <summary>
/// Called when [setup].
/// </summary>
protected virtual void OnSetup()
{
// empty
}
/// <summary>
/// Does the background work.
/// </summary>
/// <param name="cancellationToken">The ct.</param>
protected abstract void DoBackgroundWork(CancellationToken cancellationToken);
}
}

View File

@@ -0,0 +1,185 @@
using System;
using System.Threading;
namespace Swan.Threading
{
/// <summary>
/// Provides factory methods to create synchronized reader-writer locks
/// that support a generalized locking and releasing api and syntax.
/// </summary>
public static class SyncLockerFactory
{
#region Enums and Interfaces
/// <summary>
/// Enumerates the locking operations.
/// </summary>
private enum LockHolderType
{
Read,
Write,
}
/// <summary>
/// Defines methods for releasing locks.
/// </summary>
private interface ISyncReleasable
{
/// <summary>
/// Releases the writer lock.
/// </summary>
void ReleaseWriterLock();
/// <summary>
/// Releases the reader lock.
/// </summary>
void ReleaseReaderLock();
}
#endregion
#region Factory Methods
/// <summary>
/// Creates a reader-writer lock backed by a standard ReaderWriterLock.
/// </summary>
/// <returns>The synchronized locker.</returns>
public static ISyncLocker Create() => new SyncLocker();
/// <summary>
/// Creates a reader-writer lock backed by a ReaderWriterLockSlim.
/// </summary>
/// <returns>The synchronized locker.</returns>
public static ISyncLocker CreateSlim() => new SyncLockerSlim();
/// <summary>
/// Creates a reader-writer lock.
/// </summary>
/// <param name="useSlim">if set to <c>true</c> it uses the Slim version of a reader-writer lock.</param>
/// <returns>The Sync Locker.</returns>
public static ISyncLocker Create(bool useSlim) => useSlim ? CreateSlim() : Create();
#endregion
#region Private Classes
/// <summary>
/// The lock releaser. Calling the dispose method releases the lock entered by the parent SyncLocker.
/// </summary>
/// <seealso cref="System.IDisposable" />
private sealed class SyncLockReleaser : IDisposable
{
private readonly ISyncReleasable _parent;
private readonly LockHolderType _operation;
private bool _isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="SyncLockReleaser"/> class.
/// </summary>
/// <param name="parent">The parent.</param>
/// <param name="operation">The operation.</param>
public SyncLockReleaser(ISyncReleasable parent, LockHolderType operation)
{
_parent = parent;
_operation = operation;
}
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
if (_operation == LockHolderType.Read)
_parent.ReleaseReaderLock();
else
_parent.ReleaseWriterLock();
}
}
/// <summary>
/// The Sync Locker backed by a ReaderWriterLock.
/// </summary>
/// <seealso cref="ISyncLocker" />
/// <seealso cref="ISyncReleasable" />
private sealed class SyncLocker : ISyncLocker, ISyncReleasable
{
private bool _isDisposed;
private ReaderWriterLock? _locker = new ReaderWriterLock();
/// <inheritdoc />
public IDisposable AcquireReaderLock()
{
_locker?.AcquireReaderLock(Timeout.Infinite);
return new SyncLockReleaser(this, LockHolderType.Read);
}
/// <inheritdoc />
public IDisposable AcquireWriterLock()
{
_locker?.AcquireWriterLock(Timeout.Infinite);
return new SyncLockReleaser(this, LockHolderType.Write);
}
/// <inheritdoc />
public void ReleaseWriterLock() => _locker?.ReleaseWriterLock();
/// <inheritdoc />
public void ReleaseReaderLock() => _locker?.ReleaseReaderLock();
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
_locker?.ReleaseLock();
_locker = null;
}
}
/// <summary>
/// The Sync Locker backed by ReaderWriterLockSlim.
/// </summary>
/// <seealso cref="ISyncLocker" />
/// <seealso cref="ISyncReleasable" />
private sealed class SyncLockerSlim : ISyncLocker, ISyncReleasable
{
private bool _isDisposed;
private ReaderWriterLockSlim _locker
= new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
/// <inheritdoc />
public IDisposable AcquireReaderLock()
{
_locker?.EnterReadLock();
return new SyncLockReleaser(this, LockHolderType.Read);
}
/// <inheritdoc />
public IDisposable AcquireWriterLock()
{
_locker?.EnterWriteLock();
return new SyncLockReleaser(this, LockHolderType.Write);
}
/// <inheritdoc />
public void ReleaseWriterLock() => _locker?.ExitWriteLock();
/// <inheritdoc />
public void ReleaseReaderLock() => _locker?.ExitReadLock();
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
_locker?.Dispose();
_locker = null;
}
}
#endregion
}
}

View File

@@ -0,0 +1,204 @@
using System;
using System.Threading;
namespace Swan.Threading
{
/// <summary>
/// Provides a Manual Reset Event factory with a unified API.
/// </summary>
/// <example>
/// The following example shows how to use the WaitEventFactory class.
/// <code>
/// using Swan.Threading;
///
/// public class Example
/// {
/// // create a WaitEvent using the slim version
/// private static readonly IWaitEvent waitEvent = WaitEventFactory.CreateSlim(false);
///
/// public static void Main()
/// {
/// Task.Factory.StartNew(() =>
/// {
/// DoWork(1);
/// });
///
/// Task.Factory.StartNew(() =>
/// {
/// DoWork(2);
/// });
///
/// // send first signal
/// waitEvent.Complete();
/// waitEvent.Begin();
///
/// Thread.Sleep(TimeSpan.FromSeconds(2));
///
/// // send second signal
/// waitEvent.Complete();
///
/// Terminal.Readline();
/// }
///
/// public static void DoWork(int taskNumber)
/// {
/// $"Data retrieved:{taskNumber}".WriteLine();
/// waitEvent.Wait();
///
/// Thread.Sleep(TimeSpan.FromSeconds(2));
/// $"All finished up {taskNumber}".WriteLine();
/// }
/// }
/// </code>
/// </example>
public static class WaitEventFactory
{
#region Factory Methods
/// <summary>
/// Creates a Wait Event backed by a standard ManualResetEvent.
/// </summary>
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// <returns>The Wait Event.</returns>
public static IWaitEvent Create(bool isCompleted) => new WaitEvent(isCompleted);
/// <summary>
/// Creates a Wait Event backed by a ManualResetEventSlim.
/// </summary>
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// <returns>The Wait Event.</returns>
public static IWaitEvent CreateSlim(bool isCompleted) => new WaitEventSlim(isCompleted);
/// <summary>
/// Creates a Wait Event backed by a ManualResetEventSlim.
/// </summary>
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// <param name="useSlim">if set to <c>true</c> creates a slim version of the wait event.</param>
/// <returns>The Wait Event.</returns>
public static IWaitEvent Create(bool isCompleted, bool useSlim) => useSlim ? CreateSlim(isCompleted) : Create(isCompleted);
#endregion
#region Backing Classes
/// <summary>
/// Defines a WaitEvent backed by a ManualResetEvent.
/// </summary>
private class WaitEvent : IWaitEvent
{
private ManualResetEvent? _event;
/// <summary>
/// Initializes a new instance of the <see cref="WaitEvent"/> class.
/// </summary>
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
public WaitEvent(bool isCompleted)
{
_event = new ManualResetEvent(isCompleted);
}
/// <inheritdoc />
public bool IsDisposed { get; private set; }
/// <inheritdoc />
public bool IsValid
{
get
{
if (IsDisposed || _event == null)
return false;
if (_event?.SafeWaitHandle?.IsClosed ?? true)
return false;
return !(_event?.SafeWaitHandle?.IsInvalid ?? true);
}
}
/// <inheritdoc />
public bool IsCompleted => IsValid == false || (_event?.WaitOne(0) ?? true);
/// <inheritdoc />
public bool IsInProgress => !IsCompleted;
/// <inheritdoc />
public void Begin() => _event?.Reset();
/// <inheritdoc />
public void Complete() => _event?.Set();
/// <inheritdoc />
void IDisposable.Dispose()
{
if (IsDisposed) return;
IsDisposed = true;
_event?.Set();
_event?.Dispose();
_event = null;
}
/// <inheritdoc />
public void Wait() => _event?.WaitOne();
/// <inheritdoc />
public bool Wait(TimeSpan timeout) => _event?.WaitOne(timeout) ?? true;
}
/// <summary>
/// Defines a WaitEvent backed by a ManualResetEventSlim.
/// </summary>
private class WaitEventSlim : IWaitEvent
{
private ManualResetEventSlim? _event;
/// <summary>
/// Initializes a new instance of the <see cref="WaitEventSlim"/> class.
/// </summary>
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
public WaitEventSlim(bool isCompleted)
{
_event = new ManualResetEventSlim(isCompleted);
}
/// <inheritdoc />
public bool IsDisposed { get; private set; }
/// <inheritdoc />
public bool IsValid =>
!IsDisposed && _event?.WaitHandle?.SafeWaitHandle != null &&
(!_event.WaitHandle.SafeWaitHandle.IsClosed && !_event.WaitHandle.SafeWaitHandle.IsInvalid);
/// <inheritdoc />
public bool IsCompleted => IsValid == false || _event?.IsSet == true;
/// <inheritdoc />
public bool IsInProgress => !IsCompleted;
/// <inheritdoc />
public void Begin() => _event?.Reset();
/// <inheritdoc />
public void Complete() => _event?.Set();
/// <inheritdoc />
void IDisposable.Dispose()
{
if (IsDisposed) return;
IsDisposed = true;
_event?.Set();
_event?.Dispose();
_event = null;
}
/// <inheritdoc />
public void Wait() => _event?.Wait();
/// <inheritdoc />
public bool Wait(TimeSpan timeout) => _event?.Wait(timeout) ?? true;
}
#endregion
}
}

View File

@@ -0,0 +1,33 @@
namespace Swan.Threading
{
/// <summary>
/// Enumerates the different states in which a worker can be.
/// </summary>
public enum WorkerState
{
/// <summary>
/// The worker has been created and it is ready to start.
/// </summary>
Created,
/// <summary>
/// The worker is running it cycle logic.
/// </summary>
Running,
/// <summary>
/// The worker is running its delay logic.
/// </summary>
Waiting,
/// <summary>
/// The worker is in the paused or suspended state.
/// </summary>
Paused,
/// <summary>
/// The worker is stopped and ready for disposal.
/// </summary>
Stopped,
}
}