Reputation: 1
I need to implement a class that manages the lifetime of multiple resources. Those resources may have different implementations of the dipose pattern:
The task now is to dispose all resources under control of that class correctly, no matter, if IDispose or IAsyncDispose (or even none of them) is called.
How do I dispose resources with only IAsyncDisposable when IDispose gets called? How do I dispose resource with only IDispose when IAsyncDispose gets called?
How can that behavior be implemented in an abstract base class (e.g. DisposableObject) where child classes only "register" their resources and the base class takes care of disposing?
Here is my first implementation attempt:
public abstract class DisposableObject : IDisposableObject, IAsyncDisposableObject
{
private readonly DisposableCollection<IDisposable?> disposables = new();
private readonly AsyncDisposableCollection<IAsyncDisposable?> asyncDisposables = new();
public async ValueTask DisposeAsync()
{
await this.DisposeAsyncCore().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
public void Dispose()
{
this.DisposeCore();
GC.SuppressFinalize(this);
}
public bool IsDisposed { get; private set; }
bool IAsyncDisposableObject.IsDisposed => this.IsDisposed;
bool IDisposableObject.IsDisposed => this.IsDisposed;
protected void Manage(IDisposable? disposable)
{
this.disposables.Add(disposable);
}
protected void Manage(IAsyncDisposable? asyncDisposable)
{
this.asyncDisposables.Add(asyncDisposable);
}
private void DisposeCore()
{
if (this.IsDisposed) return;
this.IsDisposed = true;
this.disposables.Dispose();
this.asyncDisposables.ForEach(asyncDisposable =>
{
// dispose hybrid resources sync
if (asyncDisposable is IDisposable disposable) disposable.Dispose();
// ???
asyncDisposable?.DisposeAsync().AwaitResult();
});
}
private async ValueTask DisposeAsyncCore()
{
if (this.IsDisposed) return;
this.IsDisposed = true;
await this.asyncDisposables.TryDisposeAsync().ConfigureAwait(false);
await this.disposables.ForEachAsync(async disposable =>
{
// dispose hybrid resources async
if (disposable is IAsyncDisposable asyncDisposable) await asyncDisposable.DisposeAsync().ConfigureAwait(false);
// ???
disposable?.Dispose();
}).ConfigureAwait(false);
}
}
Upvotes: 0
Views: 251
Reputation: 457147
managed resources with both IDispose and IAsyncDispose
This is quite rare, but I'd recommend only calling DisposeAsync
in that case.
managed resources without IDispose or IAsyncDispose
You don't do anything in this case.
unmanaged resources
The proper solution in this case is to create a wrapper class just for the unmanaged resource that implements IDisposable
. Attempting to support both unmanaged resources and managed disposable resources is a fool's errand, adding considerable complexity for no benefit.
The task now is to dispose all resources under control of that class correctly, no matter, if IDispose or IAsyncDispose (or even none of them) is called.
If the DisposeAsync
implementations support disposal from arbitrary threads, then this may be possible. Of course, ensuring disposal even if no dispose is called is (again) a fool's errand and not possible. The wrapper classes handling the unmanaged resource will take care of that scenario in their finalizers.
How do I dispose resources with only IAsyncDisposable when IDispose gets called?
This is the tricky one. If the implementations allow calling DisposeAsync
from any thread, then you can toss it on the thread pool and block on it.
How do I dispose resource with only IDispose when IAsyncDispose gets called?
Just call Dispose
?
How can that behavior be implemented in an abstract base class (e.g. DisposableObject) where child classes only "register" their resources and the base class takes care of disposing?
Why would you ever want to do this? Composition wins over inheritance an all but a handful of design scenarios. That's definitely the case here.
I have a Disposables library that mostly does what you want. It just doesn't do the funky IDisposable.Dispose
=> IAsyncDisposable.DisposeAsync
thing, so you'd have to do that yourself:
public sealed class MyDisposableCollection : IDisposable, IAsyncDisposable
{
private readonly CollectionAsyncDisposable _disposables = new();
public bool TryAdd(IAsyncDisposable disposable) => _disposables.TryAdd(disposable);
public bool TryAdd(IDisposable disposable) => _disposables.TryAdd(disposable.ToAsyncDisposable());
public ValueTask DisposeAsync() => _disposables.DisposeAsync();
public void Dispose()
{
Task.Run(async () => await _disposables.DisposeAsync())
.GetAwaiter().GetResult();
}
}
Upvotes: 0