Этот вопрос специфичен для многопоточных вычислений. приложения под .NET 9.
Я часто сталкиваюсь со сценариями COM Interop, где ссылки должны располагаться в определенном порядке, а порядок их получения недетерминирован. Я также придерживаюсь мнения, что наследование интерфейса — лучший подход по сравнению с наследованием реализации в сценариях, связанных с программными конструкциями, а не с бизнес-логикой. Однако этот вопрос не касается подхода.
Я использую конкретную, абстрактную реализацию шаблона IDisposable в качестве основы для классов верхнего уровня, требующих точного контроля над порядок утилизации. Я впервые реализую шаблон IAsyncDispose и стараюсь придерживаться документации, доступной здесь. Я все еще ломаю голову над этим, поэтому не уверен, подходит ли заголовок вопроса.
internal abstract class Disposable:
IDisposable,
IAsyncDisposable
{
private bool Disposed = false;
private readonly List SafeHandleList = [];
private readonly Stack SafeHandleStack = [];
private readonly Queue SafeHandleQueue = [];
private readonly List DisposableList = [];
private readonly Stack DisposableStack = [];
private readonly Queue DisposableQueue = [];
private readonly List AsyncDisposableList = [];
private readonly Stack AsyncDisposableStack = [];
private readonly Queue AsyncDisposableQueue = [];
protected Disposable () { }
~Disposable () { this.Dispose(disposing: false); }
public bool IsDisposed => this.Disposed;
protected T AddDisposable (T disposable) where T : IDisposable
{ this.ThrowDisposedException(); this.DisposableList.Add(disposable); return disposable; }
protected T PushDisposable (T disposable) where T : IDisposable
{ this.ThrowDisposedException(); this.DisposableStack.Push(disposable); return disposable; }
protected T EnqueueDisposable (T disposable) where T : IDisposable
{ this.ThrowDisposedException(); this.DisposableQueue.Enqueue(disposable); return disposable; }
protected T AddAsyncDisposable (T asyncDisposable) where T : IAsyncDisposable
{ this.ThrowDisposedException(); this.AsyncDisposableList.Add(asyncDisposable); if (asyncDisposable is IDisposable disposable) { this.DisposableList.Add(disposable); } return asyncDisposable; }
protected T PushAsyncDisposable (T asyncDisposable) where T : IAsyncDisposable
{ this.ThrowDisposedException(); this.AsyncDisposableStack.Push(asyncDisposable); if (asyncDisposable is IDisposable disposable) { this.DisposableStack.Push(disposable); } return asyncDisposable; }
protected T EnqueueAsyncDisposable (T asyncDisposable) where T : IAsyncDisposable
{ this.ThrowDisposedException(); this.AsyncDisposableQueue.Enqueue(asyncDisposable); if (asyncDisposable is IDisposable disposable) { this.DisposableQueue.Enqueue(disposable); } return asyncDisposable; }
protected T AddSafeHandle (T safeHandle) where T : SafeHandle
{ this.ThrowDisposedException(); this.SafeHandleList.Add(safeHandle); return safeHandle; }
protected T PushSafeHandle (T disposable) where T : SafeHandle
{ this.ThrowDisposedException(); this.SafeHandleStack.Push(disposable); return disposable; }
protected T EnqueueSafeHandle (T disposable) where T : SafeHandle
{ this.ThrowDisposedException(); this.SafeHandleQueue.Enqueue(disposable); return disposable; }
public void ThrowDisposedException ()
{
if (this.Disposed)
{
var type = this.GetType();
throw new ObjectDisposedException(type.FullName, $@"Attempt to access a disposed object: [{type.FullName}].");
}
}
public void Dispose ()
{
this.Dispose(disposing: true);
GC.SuppressFinalize(obj: this);
}
protected virtual void Dispose (bool disposing)
{
if (!this.Disposed)
{
if (disposing)
{
// Dispose objects implementing [IDisposable] and [SafeHandle].
while (this.DisposableList.Count > 0) { try { this.DisposableList [0]?.Dispose(); } catch { } this.DisposableList.RemoveAt(0); }
while (this.DisposableStack.Count > 0) { try { this.DisposableStack.Pop()?.Dispose(); } catch { } }
while (this.DisposableQueue.Count > 0) { try { this.DisposableQueue.Dequeue()?.Dispose(); } catch { } }
while (this.SafeHandleList.Count > 0) { try { this.SafeHandleList [0]?.Dispose(); } catch { } this.SafeHandleList.RemoveAt(0); }
while (this.SafeHandleStack.Count > 0) { try { this.SafeHandleStack.Pop()?.Dispose(); } catch { } }
while (this.SafeHandleQueue.Count > 0) { try { this.SafeHandleQueue.Dequeue()?.Dispose(); } catch { } }
// This approach is meant to help both async and non-async consumption scenarios.
// Any objects implementing [IAsyncDisposable] as well as [IDisposable] have already been dealt with at this point.
// https://learn.microsoft.com/en-us/dotne ... e-patterns.
// It is up to the comsuming code to ensure that [DisposeAsync] is called, if needed. Example: [using (var ad = new AsyncDisposable())] vs. [await using (var ad = = new AsyncDisposable())].
}
// Dispose unmanaged resources (excluding [SafeHandle] objects).
// Free unmanaged resources (unmanaged objects), override finalizer, and set large fields to null.
this.Disposed = true;
}
}
public async ValueTask DisposeAsync ()
{
// Perform asynchronous cleanup.
await this.DisposeAsyncCore()
.ConfigureAwait(false);
// Dispose of unmanaged resources.
this.Dispose(false);
// Suppress finalization.
GC.SuppressFinalize(this);
}
protected virtual async ValueTask DisposeAsyncCore ()
{
if (!this.Disposed)
{
while (this.AsyncDisposableList.Count > 0)
{
var asyncDisposable = this.AsyncDisposableList [0];
if (asyncDisposable is not null) { try { await asyncDisposable.DisposeAsync().ConfigureAwait(false); } catch { } }
this.AsyncDisposableList.RemoveAt(0);
}
while (this.AsyncDisposableStack.Count > 0)
{
var asyncDisposable = this.AsyncDisposableStack.Pop();
if (asyncDisposable is not null) { try { await asyncDisposable.DisposeAsync().ConfigureAwait(false); } catch { } }
}
while (this.AsyncDisposableQueue.Count > 0)
{
var asyncDisposable = this.AsyncDisposableQueue.Dequeue();
if (asyncDisposable is not null) { try { await asyncDisposable.DisposeAsync().ConfigureAwait(false); } catch { } }
}
// TODO: We want to ensure that objects implementing only [IDisposable] are handled as well.
// Although there are not circular references between the dispose mthods, calling [this.Dispose()] here directly smells funny.
this.Dispose();
this.Disposed = true;
}
}
}
Хотя я старался придерживаться лучших практик, я не уверен в следующем:
- Вызов Dispose из DisposeAsyncCore. Что-то меня это не устраивает. Возможно, я не продумал это как следует. Должен ли я вместо этого вызывать виртуальную реализацию Dispose(dispositing: true|false???)?
- Есть ли какие-либо ошибки, если потребляющий код вызывает [using (var ad = new AsyncDisposable())] против [await using (var ad = new AsyncDisposable())]?
- Я упускаю что-то очевидное с точки зрения охвата? Где следует утилизировать [SafeHandle] объекты идут? и т. д.
- Любые общие комментарии по этой реализации как шаблону приветствуются.
Любые советы будут оценены.
Вот пример использования для пояснения:
public sealed class SampleTask:
Disposable
{
private readonly MemoryStream Stream1;
private readonly Excel.Application Application;
public SampleTask ()
{
// Add child objects in any desired order.
this.Application = this.AddDisposable(new Excel.Application());
this.Stream1 = this.PushAsyncDisposable(new MemoryStream());
}
// This class needs to have control over the lifetime of some
// exposed objects irrespective of their external reference count.
public Image GetImage () => this.AddDisposable(new Bitmap(10, 10));
public Excel.Worksheet GetExcelWorksheet () => this.AddDisposable(this.Application.Workbooks [0].Sheets [0]);
protected override void Dispose (bool disposing) => base.Dispose(disposing);
protected override ValueTask DisposeAsyncCore () => base.DisposeAsyncCore();
}
Подробнее здесь: https://stackoverflow.com/questions/792 ... base-class