diff --git a/CHANGELOG.md b/CHANGELOG.md index 74de9bc..c94a779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Two new boolean properties in `AsyncHostedService` lets subclasses decide whether `StartAsync` should fail when the service is stopped before starting (`FailOnSetupNotStarted`) or `SetupAsync` completes with `false` (`FailOnSetupUnsuccessful`). The default value is `true` for both properties. - Struct `Louis.Threading.InterlockedFlag` now implements `IEquatable`, as well as equality and inequality operators with `bool`. +- New struct `Louis.Threading.InterlockedReference` encapsulates an object reference, so that it is always accessed in a thread-safe fashion. ### Changes to existing features diff --git a/src/Louis/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Louis/PublicAPI/net462/PublicAPI.Unshipped.txt index 6c25b21..7f39abc 100644 --- a/src/Louis/PublicAPI/net462/PublicAPI.Unshipped.txt +++ b/src/Louis/PublicAPI/net462/PublicAPI.Unshipped.txt @@ -14,7 +14,24 @@ Louis.Threading.AsyncServiceSetupResult.NotStarted = 1 -> Louis.Threading.AsyncS Louis.Threading.AsyncServiceSetupResult.Successful = 0 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.AsyncServiceSetupResult.Unsuccessful = 2 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.InterlockedFlag.Equals(bool other) -> bool +Louis.Threading.InterlockedReference +Louis.Threading.InterlockedReference.CompareExchange(T? value, T? comparand) -> T? +Louis.Threading.InterlockedReference.Equals(Louis.Threading.InterlockedReference other) -> bool +Louis.Threading.InterlockedReference.Equals(T? other) -> bool +Louis.Threading.InterlockedReference.Exchange(T? value) -> T? +Louis.Threading.InterlockedReference.InterlockedReference() -> void +Louis.Threading.InterlockedReference.InterlockedReference(T? value) -> void +Louis.Threading.InterlockedReference.IsNull.get -> bool +Louis.Threading.InterlockedReference.Value.get -> T? +Louis.Threading.InterlockedReference.Value.set -> void +override Louis.Threading.InterlockedReference.Equals(object? obj) -> bool +override Louis.Threading.InterlockedReference.GetHashCode() -> int +override Louis.Threading.InterlockedReference.ToString() -> string! static Louis.Threading.InterlockedFlag.operator !=(Louis.Threading.InterlockedFlag a, bool b) -> bool static Louis.Threading.InterlockedFlag.operator ==(Louis.Threading.InterlockedFlag a, bool b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, T? b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, T? b) -> bool virtual Louis.Threading.AsyncService.LogSetupCompleted(bool success) -> void virtual Louis.Threading.AsyncService.SetupAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask diff --git a/src/Louis/PublicAPI/net47/PublicAPI.Unshipped.txt b/src/Louis/PublicAPI/net47/PublicAPI.Unshipped.txt index 6c25b21..7f39abc 100644 --- a/src/Louis/PublicAPI/net47/PublicAPI.Unshipped.txt +++ b/src/Louis/PublicAPI/net47/PublicAPI.Unshipped.txt @@ -14,7 +14,24 @@ Louis.Threading.AsyncServiceSetupResult.NotStarted = 1 -> Louis.Threading.AsyncS Louis.Threading.AsyncServiceSetupResult.Successful = 0 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.AsyncServiceSetupResult.Unsuccessful = 2 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.InterlockedFlag.Equals(bool other) -> bool +Louis.Threading.InterlockedReference +Louis.Threading.InterlockedReference.CompareExchange(T? value, T? comparand) -> T? +Louis.Threading.InterlockedReference.Equals(Louis.Threading.InterlockedReference other) -> bool +Louis.Threading.InterlockedReference.Equals(T? other) -> bool +Louis.Threading.InterlockedReference.Exchange(T? value) -> T? +Louis.Threading.InterlockedReference.InterlockedReference() -> void +Louis.Threading.InterlockedReference.InterlockedReference(T? value) -> void +Louis.Threading.InterlockedReference.IsNull.get -> bool +Louis.Threading.InterlockedReference.Value.get -> T? +Louis.Threading.InterlockedReference.Value.set -> void +override Louis.Threading.InterlockedReference.Equals(object? obj) -> bool +override Louis.Threading.InterlockedReference.GetHashCode() -> int +override Louis.Threading.InterlockedReference.ToString() -> string! static Louis.Threading.InterlockedFlag.operator !=(Louis.Threading.InterlockedFlag a, bool b) -> bool static Louis.Threading.InterlockedFlag.operator ==(Louis.Threading.InterlockedFlag a, bool b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, T? b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, T? b) -> bool virtual Louis.Threading.AsyncService.LogSetupCompleted(bool success) -> void virtual Louis.Threading.AsyncService.SetupAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask diff --git a/src/Louis/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Louis/PublicAPI/net6.0/PublicAPI.Unshipped.txt index 6c25b21..7f39abc 100644 --- a/src/Louis/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/src/Louis/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -14,7 +14,24 @@ Louis.Threading.AsyncServiceSetupResult.NotStarted = 1 -> Louis.Threading.AsyncS Louis.Threading.AsyncServiceSetupResult.Successful = 0 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.AsyncServiceSetupResult.Unsuccessful = 2 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.InterlockedFlag.Equals(bool other) -> bool +Louis.Threading.InterlockedReference +Louis.Threading.InterlockedReference.CompareExchange(T? value, T? comparand) -> T? +Louis.Threading.InterlockedReference.Equals(Louis.Threading.InterlockedReference other) -> bool +Louis.Threading.InterlockedReference.Equals(T? other) -> bool +Louis.Threading.InterlockedReference.Exchange(T? value) -> T? +Louis.Threading.InterlockedReference.InterlockedReference() -> void +Louis.Threading.InterlockedReference.InterlockedReference(T? value) -> void +Louis.Threading.InterlockedReference.IsNull.get -> bool +Louis.Threading.InterlockedReference.Value.get -> T? +Louis.Threading.InterlockedReference.Value.set -> void +override Louis.Threading.InterlockedReference.Equals(object? obj) -> bool +override Louis.Threading.InterlockedReference.GetHashCode() -> int +override Louis.Threading.InterlockedReference.ToString() -> string! static Louis.Threading.InterlockedFlag.operator !=(Louis.Threading.InterlockedFlag a, bool b) -> bool static Louis.Threading.InterlockedFlag.operator ==(Louis.Threading.InterlockedFlag a, bool b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, T? b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, T? b) -> bool virtual Louis.Threading.AsyncService.LogSetupCompleted(bool success) -> void virtual Louis.Threading.AsyncService.SetupAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask diff --git a/src/Louis/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Louis/PublicAPI/net7.0/PublicAPI.Unshipped.txt index bc5e9cc..86dff26 100644 --- a/src/Louis/PublicAPI/net7.0/PublicAPI.Unshipped.txt +++ b/src/Louis/PublicAPI/net7.0/PublicAPI.Unshipped.txt @@ -15,9 +15,26 @@ Louis.Threading.AsyncServiceSetupResult.NotStarted = 1 -> Louis.Threading.AsyncS Louis.Threading.AsyncServiceSetupResult.Successful = 0 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.AsyncServiceSetupResult.Unsuccessful = 2 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.InterlockedFlag.Equals(bool other) -> bool +Louis.Threading.InterlockedReference +Louis.Threading.InterlockedReference.CompareExchange(T? value, T? comparand) -> T? +Louis.Threading.InterlockedReference.Equals(Louis.Threading.InterlockedReference other) -> bool +Louis.Threading.InterlockedReference.Equals(T? other) -> bool +Louis.Threading.InterlockedReference.Exchange(T? value) -> T? +Louis.Threading.InterlockedReference.InterlockedReference() -> void +Louis.Threading.InterlockedReference.InterlockedReference(T? value) -> void +Louis.Threading.InterlockedReference.IsNull.get -> bool +Louis.Threading.InterlockedReference.Value.get -> T? +Louis.Threading.InterlockedReference.Value.set -> void +override Louis.Threading.InterlockedReference.Equals(object? obj) -> bool +override Louis.Threading.InterlockedReference.GetHashCode() -> int +override Louis.Threading.InterlockedReference.ToString() -> string! static Louis.ComponentModel.SimpleStringConverter.AddToTypeDescriptor() -> void Louis.Threading.AsyncService.StopAndWaitAsync() -> System.Threading.Tasks.Task! static Louis.Threading.InterlockedFlag.operator !=(Louis.Threading.InterlockedFlag a, bool b) -> bool static Louis.Threading.InterlockedFlag.operator ==(Louis.Threading.InterlockedFlag a, bool b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, T? b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, T? b) -> bool virtual Louis.Threading.AsyncService.LogSetupCompleted(bool success) -> void virtual Louis.Threading.AsyncService.SetupAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask diff --git a/src/Louis/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Louis/PublicAPI/net8.0/PublicAPI.Unshipped.txt index bc5e9cc..86dff26 100644 --- a/src/Louis/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Louis/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -15,9 +15,26 @@ Louis.Threading.AsyncServiceSetupResult.NotStarted = 1 -> Louis.Threading.AsyncS Louis.Threading.AsyncServiceSetupResult.Successful = 0 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.AsyncServiceSetupResult.Unsuccessful = 2 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.InterlockedFlag.Equals(bool other) -> bool +Louis.Threading.InterlockedReference +Louis.Threading.InterlockedReference.CompareExchange(T? value, T? comparand) -> T? +Louis.Threading.InterlockedReference.Equals(Louis.Threading.InterlockedReference other) -> bool +Louis.Threading.InterlockedReference.Equals(T? other) -> bool +Louis.Threading.InterlockedReference.Exchange(T? value) -> T? +Louis.Threading.InterlockedReference.InterlockedReference() -> void +Louis.Threading.InterlockedReference.InterlockedReference(T? value) -> void +Louis.Threading.InterlockedReference.IsNull.get -> bool +Louis.Threading.InterlockedReference.Value.get -> T? +Louis.Threading.InterlockedReference.Value.set -> void +override Louis.Threading.InterlockedReference.Equals(object? obj) -> bool +override Louis.Threading.InterlockedReference.GetHashCode() -> int +override Louis.Threading.InterlockedReference.ToString() -> string! static Louis.ComponentModel.SimpleStringConverter.AddToTypeDescriptor() -> void Louis.Threading.AsyncService.StopAndWaitAsync() -> System.Threading.Tasks.Task! static Louis.Threading.InterlockedFlag.operator !=(Louis.Threading.InterlockedFlag a, bool b) -> bool static Louis.Threading.InterlockedFlag.operator ==(Louis.Threading.InterlockedFlag a, bool b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, T? b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, T? b) -> bool virtual Louis.Threading.AsyncService.LogSetupCompleted(bool success) -> void virtual Louis.Threading.AsyncService.SetupAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask diff --git a/src/Louis/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Louis/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 6c25b21..7f39abc 100644 --- a/src/Louis/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Louis/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -14,7 +14,24 @@ Louis.Threading.AsyncServiceSetupResult.NotStarted = 1 -> Louis.Threading.AsyncS Louis.Threading.AsyncServiceSetupResult.Successful = 0 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.AsyncServiceSetupResult.Unsuccessful = 2 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.InterlockedFlag.Equals(bool other) -> bool +Louis.Threading.InterlockedReference +Louis.Threading.InterlockedReference.CompareExchange(T? value, T? comparand) -> T? +Louis.Threading.InterlockedReference.Equals(Louis.Threading.InterlockedReference other) -> bool +Louis.Threading.InterlockedReference.Equals(T? other) -> bool +Louis.Threading.InterlockedReference.Exchange(T? value) -> T? +Louis.Threading.InterlockedReference.InterlockedReference() -> void +Louis.Threading.InterlockedReference.InterlockedReference(T? value) -> void +Louis.Threading.InterlockedReference.IsNull.get -> bool +Louis.Threading.InterlockedReference.Value.get -> T? +Louis.Threading.InterlockedReference.Value.set -> void +override Louis.Threading.InterlockedReference.Equals(object? obj) -> bool +override Louis.Threading.InterlockedReference.GetHashCode() -> int +override Louis.Threading.InterlockedReference.ToString() -> string! static Louis.Threading.InterlockedFlag.operator !=(Louis.Threading.InterlockedFlag a, bool b) -> bool static Louis.Threading.InterlockedFlag.operator ==(Louis.Threading.InterlockedFlag a, bool b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, T? b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, T? b) -> bool virtual Louis.Threading.AsyncService.LogSetupCompleted(bool success) -> void virtual Louis.Threading.AsyncService.SetupAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask diff --git a/src/Louis/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/src/Louis/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index 6c25b21..7f39abc 100644 --- a/src/Louis/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/Louis/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -14,7 +14,24 @@ Louis.Threading.AsyncServiceSetupResult.NotStarted = 1 -> Louis.Threading.AsyncS Louis.Threading.AsyncServiceSetupResult.Successful = 0 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.AsyncServiceSetupResult.Unsuccessful = 2 -> Louis.Threading.AsyncServiceSetupResult Louis.Threading.InterlockedFlag.Equals(bool other) -> bool +Louis.Threading.InterlockedReference +Louis.Threading.InterlockedReference.CompareExchange(T? value, T? comparand) -> T? +Louis.Threading.InterlockedReference.Equals(Louis.Threading.InterlockedReference other) -> bool +Louis.Threading.InterlockedReference.Equals(T? other) -> bool +Louis.Threading.InterlockedReference.Exchange(T? value) -> T? +Louis.Threading.InterlockedReference.InterlockedReference() -> void +Louis.Threading.InterlockedReference.InterlockedReference(T? value) -> void +Louis.Threading.InterlockedReference.IsNull.get -> bool +Louis.Threading.InterlockedReference.Value.get -> T? +Louis.Threading.InterlockedReference.Value.set -> void +override Louis.Threading.InterlockedReference.Equals(object? obj) -> bool +override Louis.Threading.InterlockedReference.GetHashCode() -> int +override Louis.Threading.InterlockedReference.ToString() -> string! static Louis.Threading.InterlockedFlag.operator !=(Louis.Threading.InterlockedFlag a, bool b) -> bool static Louis.Threading.InterlockedFlag.operator ==(Louis.Threading.InterlockedFlag a, bool b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator !=(Louis.Threading.InterlockedReference a, T? b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, Louis.Threading.InterlockedReference b) -> bool +static Louis.Threading.InterlockedReference.operator ==(Louis.Threading.InterlockedReference a, T? b) -> bool virtual Louis.Threading.AsyncService.LogSetupCompleted(bool success) -> void virtual Louis.Threading.AsyncService.SetupAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask diff --git a/src/Louis/Threading/InterlockedReference`1.cs b/src/Louis/Threading/InterlockedReference`1.cs new file mode 100644 index 0000000..802f372 --- /dev/null +++ b/src/Louis/Threading/InterlockedReference`1.cs @@ -0,0 +1,116 @@ +// Copyright (c) Tenacom and contributors. Licensed under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Threading; + +namespace Louis.Threading; + +/// +/// Implements thread-safe management of an object reference. +/// +/// The type of the stored reference. +public struct InterlockedReference : IEquatable>, IEquatable + where T : class +{ + private T? _value; + + /// + /// Initializes a new instance of the struct. + /// + /// The initial value of the reference. + public InterlockedReference(T? value) + { + _value = value; + } + + /// + /// Gets or sets the reference stored in an . + /// This property if thread-safe. + /// + public T? Value + { + get => Interlocked.CompareExchange(ref _value, null, null); + set => _ = Interlocked.Exchange(ref _value, value); + } + + /// + /// Gets a value indicating whether the reference stored in an is . + /// + /// + /// if the reference is ; + /// otherwise. + /// + public bool IsNull => Interlocked.CompareExchange(ref _value, null, null) == null; + + /// + /// Determines whether two specified interlocked references have the same value. + /// + /// The first instance to compare. + /// The second instance to compare. + /// if the value of is the same as the value of ; + /// otherwise, . + public static bool operator ==(InterlockedReference a, InterlockedReference b) => ReferenceEquals(a.Value, b.Value); + + /// + /// Determines whether two specified interlocked references have different values. + /// + /// The first instance to compare. + /// The second instance to compare. + /// if the value of is different from the value of ; + /// otherwise, . + public static bool operator !=(InterlockedReference a, InterlockedReference b) => !ReferenceEquals(a.Value, b.Value); + + /// + /// Determines whether the value of a specified interlocked references is the same as a specified value. + /// + /// The interlocked reference to compare. + /// The reference to compare. + /// if the value of is the same as the value of ; + /// otherwise, . + public static bool operator ==(InterlockedReference a, T? b) => ReferenceEquals(a.Value, b); + + /// + /// Determines whether the value of a specified interlocked reference is different from a specified value. + /// + /// The interlocked reference to compare. + /// The reference to compare. + /// if the value of is different from the value of ; + /// otherwise, . + public static bool operator !=(InterlockedReference a, T? b) => !ReferenceEquals(a.Value, b); + + /// + public override bool Equals(object? obj) => obj switch { + null => Interlocked.CompareExchange(ref _value, null, null) == null, + T value => Interlocked.CompareExchange(ref _value, null, null) == value, + InterlockedReference other => Interlocked.CompareExchange(ref _value, null, null) == other.Value, + _ => false, + }; + + /// + public bool Equals(InterlockedReference other) => ReferenceEquals(other.Value, Value); + + /// + public bool Equals(T? other) => Interlocked.CompareExchange(ref _value, null, null) == other; + + /// + public override int GetHashCode() => HashCode.Combine(Value); + + /// + public override string ToString() => Value?.ToString() ?? string.Empty; + + /// + /// Sets the value of an interlocked reference. + /// + /// The value to set. + /// The previous value of this instance. + public T? Exchange(T? value) => Interlocked.Exchange(ref _value, value); + + /// + /// Compares the value of an interlocked reference to a given comparand and, if they are equal, replaces it with a given value. + /// + /// The value that replaces the destination value if the comparison by reference results in equality. + /// The value that is compared by reference to the value of this instance. + /// The original value of this instance. + public T? CompareExchange(T? value, T? comparand) => Interlocked.CompareExchange(ref _value, value, comparand); +}