Вот решение для многоразовой поддержки асинхронного удаления. В связи с тем, что .NET Core 3.0 еще не выпущен, я предоставлю код как для текущей версии C # (7.3), так и для бета-версии (8.0).
Как только IDisposable.Dispose()
вызывается для объекта, он не блокируется и обеспечит удаление сразу после завершения всех задач.
Исходный код (текущая версия C #, без IAsyncDisposable
)
Связанные с использованием:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
Интерфейс, который можно утилизировать после выполнения всех задач отслеживания:
public interface ITrackingDisposable : IDisposable
{
//The implementation of the actual disposings
Task FinishDisposeAsync();
}
Диспетчер, который отслеживает все запущенные задачи и вызывает отложенное удаление в соответствующий момент времени:
public class TrackingDisposer : IDisposable
{
private readonly LinkedList<Task> _tasks = new LinkedList<Task>();
private readonly ITrackingDisposable _target;
public bool IsDisposed { get; private set; } = false;
//The supported class must implement ITrackingDisposable
public TrackingDisposer(ITrackingDisposable target)
=> _target = target ?? throw new ArgumentNullException();
//Add a task to the tracking list, returns false if disposed
//Without return value
public bool Track(Func<Task> func, out Task result)
{
lock (_tasks)
{
if (IsDisposed)
{
result = null;
return false;
}
var task = func();
var node = _tasks.AddFirst(task);
async Task ending()
{
await task;
var dispose = false;
lock (_tasks)
{
_tasks.Remove(node);
dispose = IsDisposed && _tasks.Count == 0;
}
if (dispose)
{
await _target.FinishDisposeAsync();
}
}
result = ending();
}
return true;
}
//With return value
public bool Track<TResult>(Func<Task<TResult>> func, out Task<TResult> result)
{
lock (_tasks)
{
if (IsDisposed)
{
result = null;
return false;
}
var task = func();
var node = _tasks.AddFirst(task);
async Task<TResult> ending()
{
var result = await task;
var dispose = false;
lock (_tasks)
{
_tasks.Remove(node);
dispose = IsDisposed && _tasks.Count == 0;
}
if (dispose)
{
await _target.FinishDisposeAsync();
}
return result;
}
result = ending();
}
return true;
}
//The entry of applying for dispose
public void Dispose()
{
var dispose = false;
lock (_tasks)
{
if (IsDisposed)
{
return;
}
IsDisposed = true;
dispose = _tasks.Count == 0;
}
if (dispose)
{
_target.FinishDisposeAsync();
}
}
}
Базовый класс, упрощающий реализацию:
public abstract class TrackingDisposable : ITrackingDisposable
{
private readonly TrackingDisposer _disposer;
public TrackingDisposable()
=> _disposer = new TrackingDisposer(this);
protected virtual void FinishDispose() { }
protected virtual Task FinishDisposeAsync()
=> Task.CompletedTask;
Task ITrackingDisposable.FinishDisposeAsync()
{
FinishDispose();
return FinishDisposeAsync();
}
public void Dispose()
=> _disposer.Dispose();
protected Task Track(Func<Task> func)
=> _disposer.Track(func, out var result)
? result
: throw new ObjectDisposedException(nameof(TrackingDisposable));
protected Task<TResult> Track<TResult>(Func<Task<TResult>> func)
=> _disposer.Track(func, out var result)
? result
: throw new ObjectDisposedException(nameof(TrackingDisposable));
}
Демонстрация и тестовый вывод
Класс тестирования:
internal sealed class TestDisposingObject : TrackingDisposable
{
public Task Job0Async() => Track(async () =>
{
await Task.Delay(200);
Console.WriteLine("Job0 done.");
});
public Task<string> Job1Async(int ms) => Track(async () =>
{
await Task.Delay(ms);
return "Job1 done.";
});
protected override void FinishDispose()
=> Console.WriteLine("Disposed.");
}
Main:
internal static class Program
{
private static async Task Main()
{
var result0 = default(Task);
var result1 = default(Task);
var obj = new TestDisposingObject();
result0 = obj.Job0Async();
result1 = obj.Job1Async(100).ContinueWith(r => Console.WriteLine(r.Result));
obj.Dispose();
Console.WriteLine("Waiting For jobs done...");
await Task.WhenAll(result0, result1);
}
}
Выход:
Waiting For jobs done...
Job1 done.
Job0 done.
Disposed.
Дополнительно, C # 8.0 (с IAsyncDisposable
)
Заменить определение типа следующим:
public interface ITrackingDisposable : IDisposable, IAsyncDisposable
{
Task FinishDisposeAsync();
}
public class TrackingDisposer : IDisposable, IAsyncDisposable
{
private readonly LinkedList<Task> _tasks = new LinkedList<Task>();
private readonly ITrackingDisposable _target;
private readonly TaskCompletionSource<object> _disposing = new TaskCompletionSource<object>();
public bool IsDisposed { get; private set; } = false;
public TrackingDisposer(ITrackingDisposable target)
=> _target = target ?? throw new ArgumentNullException();
public bool Track(Func<Task> func, out Task result)
{
lock (_tasks)
{
if (IsDisposed)
{
result = null;
return false;
}
var task = func();
var node = _tasks.AddFirst(task);
async Task ending()
{
await task;
var dispose = false;
lock (_tasks)
{
_tasks.Remove(node);
dispose = IsDisposed && _tasks.Count == 0;
}
if (dispose)
{
await _target.FinishDisposeAsync();
_disposing.SetResult(null);
}
}
result = ending();
}
return true;
}
public bool Track<TResult>(Func<Task<TResult>> func, out Task<TResult> result)
{
lock (_tasks)
{
if (IsDisposed)
{
result = null;
return false;
}
var task = func();
var node = _tasks.AddFirst(task);
async Task<TResult> ending()
{
var result = await task;
var dispose = false;
lock (_tasks)
{
_tasks.Remove(node);
dispose = IsDisposed && _tasks.Count == 0;
}
if (dispose)
{
await _target.FinishDisposeAsync();
_disposing.SetResult(null);
}
return result;
}
result = ending();
}
return true;
}
public void Dispose()
{
var dispose = false;
lock (_tasks)
{
if (IsDisposed)
{
return;
}
IsDisposed = true;
dispose = _tasks.Count == 0;
}
if (dispose)
{
_target.FinishDisposeAsync();
_disposing.SetResult(null);
}
}
public ValueTask DisposeAsync()
{
Dispose();
return new ValueTask(_disposing.Task);
}
}
public abstract class TrackingDisposable : ITrackingDisposable
{
private readonly TrackingDisposer _disposer;
public TrackingDisposable()
=> _disposer = new TrackingDisposer(this);
protected virtual void FinishDispose() { }
protected virtual Task FinishDisposeAsync()
=> Task.CompletedTask;
Task ITrackingDisposable.FinishDisposeAsync()
{
FinishDispose();
return FinishDisposeAsync();
}
public void Dispose()
=> _disposer.Dispose();
public ValueTask DisposeAsync() => _disposer.DisposeAsync();
protected Task Track(Func<Task> func)
=> _disposer.Track(func, out var result)
? result
: throw new ObjectDisposedException(nameof(TrackingDisposable));
protected Task<TResult> Track<TResult>(Func<Task<TResult>> func)
=> _disposer.Track(func, out var result)
? result
: throw new ObjectDisposedException(nameof(TrackingDisposable));
}
Test Main:
internal static class Program
{
private static async Task Main()
{
await using var obj = new TestDisposingObject();
_ = obj.Job0Async();
_ = obj.Job1Async(100).ContinueWith(r => Console.WriteLine(r.Result));
Console.WriteLine("Waiting For jobs done...");
}
}