我有一个带有异步方法的一次性类。
class Gateway : IDisposable {
public Gateway() {}
public void Dispose() {}
public async Task<Data> Request1 () {...}
public async Task<Data> Request2 () {...}
public async Task<Data> Request3 () {...}
}
我需要Dispose等待所有正在运行的请求完成。
因此,我需要跟踪所有正在运行的任务,还是使用AsyncEx的AsyncLock
或其他功能?
已更新
我可以看到有人害怕阻止Dispose。然后,我们可以制作Task WaitForCompletionAsync()
或Task CancelAllAsync()
方法。
答案 0 :(得分:1)
暂时,您必须添加用户必须调用的CloseAsync
方法。
C#8.0 发布后,您就可以依靠IAsyncDisposable
Interface及其语言支持:
await using (var asyncDisposable GetAsyncDisposable())
{
// ...
} // await asyncDisposable.DisposeAsync()
答案 1 :(得分:1)
这是可重复使用的异步处理支持的解决方案。由于.NET Core 3.0尚未发布,我将提供当前C#版本(7.3)和Beta(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.");
}
主要:
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));
}
主要测试:
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...");
}
}
答案 2 :(得分:0)
这里的问题是Dispose()
还没有异步版本。因此,您必须问自己-调用Dispose()
或using
块结束时会发生什么...?换句话说,要求是什么?
您可以要求Dispose
等待所有未完成的任务,然后再进行工作。但是Dispose不能使用await
(它不是异步的)。最好的办法是调用Result
来强制任务完成,但这将是阻塞调用,并且如果任何异步任务正在等待其他任何事情,则很容易死锁。
相反,我建议满足以下要求:当呼叫者呼叫Dispose()
时,该呼叫将标记要处置的网关,然后立即返回,请确保处置机制将在上一个任务启动时自行激活已经完成。
如果该要求足够,则 是可能的,但是有点混乱。方法如下:
每次调用方法(例如Request
)时,将返回的任务“包装”到另一个任务中,该任务包括检查呼叫者是否已请求处置网关。 p>
如果已请求处置,请立即进行处置,然后再在那里将其标记为已完成。因此,当呼叫者等待任务时,它将强制处置。
这是我的实现。我告诉你这很丑。
class Gateway : IDisposable
{
protected readonly HttpClient _client = new HttpClient(); //an inner class that must be disposed when Gateway disposes
protected bool _disposalRequested = false;
protected bool _disposalCompleted = false;
protected int _tasksRunning = 0;
public void Dispose()
{
Console.WriteLine("Dispose() called.");
_disposalRequested = true;
if (_tasksRunning == 0)
{
Console.WriteLine("No running tasks, so disposing immediately.");
DisposeInternal();
}
else
{
Console.WriteLine("There are running tasks, so disposal shall be deferred.");
}
}
protected void DisposeInternal()
{
if (!_disposalCompleted)
{
Console.WriteLine("Disposing");
_client.Dispose();
_disposalCompleted = true;
}
}
protected async Task<T> AddDisposeWrapper<T>(Func<Task<T>> func)
{
if (_disposalRequested) throw new ObjectDisposedException("Disposal has already been requested. No new requests can be handled at this point.");
_tasksRunning++;
var result = await func();
_tasksRunning--;
await DisposalCheck();
return result;
}
protected async Task DisposalCheck()
{
if (_disposalRequested) DisposeInternal();
}
public Task<Data> Request1()
{
return AddDisposeWrapper
(
Request1Internal
);
}
public Task<Data> Request2()
{
return AddDisposeWrapper
(
Request2Internal
);
}
protected async Task<Data> Request1Internal()
{
Console.WriteLine("Performing Request1 (slow)");
await Task.Delay(3000);
Console.WriteLine("Request1 has finished. Returning new Data.");
return new Data();
}
protected async Task<Data> Request2Internal()
{
Console.WriteLine("Performing Request2 (fast)");
await Task.Delay(1);
Console.WriteLine("Request2 has finished. Returning new Data.");
return new Data();
}
}
下面是一些测试代码:
public class Program
{
public static async Task Test1()
{
Task<Data> task;
using (var gateway = new Gateway())
{
task = gateway.Request1();
await Task.Delay(1000);
}
var data = await task;
Console.WriteLine("Test 1 is complete.");
}
public static async Task Test2()
{
Task<Data> task;
using (var gateway = new Gateway())
{
task = gateway.Request2();
await Task.Delay(1000);
}
var data = await task;
Console.WriteLine("Test 2 is complete.");
}
public static async Task MainAsync()
{
await Test1();
await Test2();
}
public static void Main()
{
MainAsync().GetAwaiter().GetResult();
Console.WriteLine("Run completed at {0:yyyy-MM-dd HH:mm:ss}", DateTime.Now);
}
}
这是输出:
Performing Request1 (slow)
Dispose() called.
There are running tasks, so disposal shall be deferred.
Request1 has finished. Returning new Data.
Disposing
Test 1 is complete.
Performing Request2 (fast)
Request2 has finished. Returning new Data.
Dispose() called.
No running tasks, so disposing immediately.
Disposing
Test 2 is complete.
Run completed at 2019-05-15 00:34:46
如果您想尝试一下,这里是我的小提琴:Link
我真的不建议这样做(如果要处置某些东西,您应该更好地控制它的寿命),但是为您编写此代码很有趣。
注意:由于使用了引用计数,因此需要额外的工作才能使此解决方案具有线程安全性,或使其能够适应网关的请求方法之一引发异常的情况。
答案 3 :(得分:0)
处置和等待完成是不同的事情。因此,我宁愿在任务仍在运行时抛出异常。
我用Nito.AsyncEx.AsyncConditionVariable
写了一个例子。我没有测试过,但是我认为它应该可以工作。只需使用Completion.WaitAsync()
。
我也推荐这篇文章:https://blog.stephencleary.com/2013/03/async-oop-6-disposal.html
class Gateway : IDisposable {
private int runningTaskCount;
public AsyncConditionVariable Completion { get; } = new AsyncConditionVariable( new AsyncLock() );
public Gateway() {
}
public void Dispose() {
if (runningTaskCount != 0) throw new InvalidOperationException( "You can not call this method when tasks are running" );
}
public async Task<Data> Request1 () {
BeginTask();
...
EndTask();
}
private void BeginTask() {
Interlocked.Increment( ref runningTaskCount );
}
private void EndTask() {
var result = Interlocked.Decrement( ref runningTaskCount );
if (result == 0) Completion.NotifyAll();
}
}