Last active
October 5, 2023 11:53
-
-
Save ArtemAvramenko/1c9d4d90a05dcbbe24d0c64848f48a50 to your computer and use it in GitHub Desktop.
Lazy pattern implementation with asynchronous locks that release the thread for waiting time
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://stackoverflow.com/questions/17975609/lazy-load-properties-with-async/41141728#41141728 | |
#nullable enable | |
public class AsyncLazy<T> | |
{ | |
private Func<Task<T>>? _valueFactory; | |
private volatile SemaphoreSlim? _semaphore = new(1, 1); | |
private volatile Boxed? _boxed; | |
public AsyncLazy(Func<Task<T>> valueFactory) | |
{ | |
_valueFactory = valueFactory ?? throw new ArgumentNullException(nameof(valueFactory)); | |
} | |
public Task<T> Value => GetValue(); | |
public bool IsValueCreated => _boxed != null && _boxed.Exception == null; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private async Task<T> GetValue() | |
{ | |
var result = _boxed; | |
if (result == null) | |
{ | |
var semaphore = _semaphore; | |
if (semaphore != null) | |
{ | |
await semaphore.WaitAsync(); | |
try | |
{ | |
if (_boxed == null) | |
{ | |
try | |
{ | |
var value = await _valueFactory!(); | |
_boxed = new Boxed() { Value = value }; | |
} | |
catch (Exception ex) | |
{ | |
_boxed = new Boxed() { Exception = ExceptionDispatchInfo.Capture(ex) }; | |
} | |
finally | |
{ | |
_semaphore = null; | |
_valueFactory = null; | |
} | |
} | |
} | |
finally | |
{ | |
semaphore.Release(); | |
} | |
} | |
result = _boxed; | |
} | |
result!.Exception?.Throw(); | |
return result.Value!; | |
} | |
private class Boxed | |
{ | |
public T? Value { get; init; } | |
public ExceptionDispatchInfo? Exception { get; init; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This field should be declared as
volatile
, to eliminate the possibility of a thread seeing a partially initializedT
instance. See: The need for volatile modifier in double checked locking in .NET.