I'm using a specialization of Stephen Cleary's AsyncLazy implementation, from his blog.
/// <summary>
/// Provides support for asynchronous lazy initialization.
/// This type is fully thread-safe.
/// </summary>
/// <typeparam name="T">
/// The type of object that is being asynchronously initialized.
/// </typeparam>
public sealed class AsyncLazy<T>
{
/// <summary>
/// The underlying lazy task.
/// </summary>
private readonly Lazy<Task<T>> instance;
/// <summary>
/// Initializes a new instance of the
/// <see cref="AsyncLazy<T>"/> class.
/// </summary>
/// <param name="factory">
/// The delegate that is invoked on a background thread to produce
/// the value when it is needed.
/// </param>
/// <param name="start">
/// If <c>true</c> commence initialization immediately.
/// </param>
public AsyncLazy(Func<T> factory, bool start = false)
{
this.instance = new Lazy<Task<T>>(() => Task.Run(factory));
if (start)
{
this.Start();
}
}
/// <summary>
/// Initializes a new instance of the
/// <see cref="AsyncLazy<T>"/> class.
/// </summary>
/// <param name="factory">
/// The asynchronous delegate that is invoked on a background
/// thread to produce the value when it is needed.
/// </param>
/// <param name="start">
/// If <c>true</c> commence initialization immediately.
/// </param>
public AsyncLazy(Func<Task<T>> factory, bool start = false)
{
this.instance = new Lazy<Task<T>>(() => Task.Run(factory));
if (start)
{
this.Start();
}
}
/// <summary>
/// Asynchronous infrastructure support.
/// This method permits instances of
/// <see cref="AsyncLazy<T>"/> to be await'ed.
/// </summary>
public TaskAwaiter<T> GetAwaiter()
{
return this.instance.Value.GetAwaiter();
}
/// <summary>
/// Starts the asynchronous initialization,
/// if it has not already started.
/// </summary>
public void Start()
{
var unused = this.instance.Value;
}
}
This is great code and I really appreciate how easy it is to use. i.e.
class SomeClass
{
private readonly AsyncLazy<Thing> theThing = new AsyncLazy<Thing>(
() => new Thing());
void SomeMethod()
{
var thing = await theThing;
// ...
}
}
Now my question,
Suppose that SomeClass
inherits from a class that implements IDisposable
and that Thing
implements IDisposable
. We'd have skeleton implementation like this,
class SomeClass : SomeDisposableBase
{
private readonly AsyncLazy<Thing> theThing = new AsyncLazy<Thing>(
() => new Thing());
protected override void Dispose(bool disposing)
{
if (disposing)
{
// What do I do with theThing?
}
base.Dispose(disposing);
}
}
So, what do I do with theThing
in the Dispose
override? Should I extend AsyncLazy<T>
to have a new property?
// ...
public bool IsStarted
{
get
{
return this.instance.IsValueCreated;
}
}
// ...
Should I change AsyncLazy<T>
to implement IDisposable
?
Have I misunderstood and I don't need to worry?
Should I do something else?
Stephen Toub's version of this class inherits from Lazy<Task<T>>
, so you get the IsValueCreated
property automatically.
Alternatively, you could expose the IsValueCreated
property from the private field:
public sealed class AsyncLazy<T>
{
private readonly Lazy<Task<T>> instance;
...
public bool IsValueCreated
{
get { return instance.IsValueCreated; }
}
}
For consistency with the built-in Lazy<T>
type, I'd avoid renaming the property to IsStarted
.
You can use a bool
inside the AsyncLazy<T>
initialization to know if theThing
has been initialized
class SomeClass : SomeDisposableBase
{
public SomeClass()
{
theThing = new AsyncLazy<Thing>(() =>
{
_isInitialized = true;
return new Thing();
}
}
private bool _isInitialized;
private readonly AsyncLazy<Thing> theThing;
protected override void Dispose(bool disposing)
{
if (disposing && _isInitialized)
{
// Dispose Thing
}
base.Dispose(disposing);
}
}
Although, if this pattern occurs in your code more than once, then i would definitely extend AsyncLazy
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With