Den*_*i35 11 .net c# multithreading asynchronous async-await
我有异步方法的一次性类。
class Gateway : IDisposable {
public Gateway() {}
public void Dispose() {}
public async Task<Data> Request1 () {...}
public async Task<Data> Request2 () {...}
public async Task<Data> Request3 () {...}
}
Run Code Online (Sandbox Code Playgroud)
我需要 Dispose 等待所有正在运行的请求完成。
那么,要么我需要跟踪所有正在运行的任务,要么使用AsyncLockAsyncEx 或其他东西?
更新
正如我所看到的,有人害怕阻止 Dispose。然后我们可以制作Task WaitForCompletionAsync()或Task CancelAllAsync()方法。
Pau*_*ado 10
目前,您必须添加一个 CloseAsync用户必须调用的方法。
一旦C# 8.0发布,您就可以依赖IAsyncDisposable接口及其语言支持:
await using (var asyncDisposable = GetAsyncDisposable())
{
// ...
} // await asyncDisposable.DisposeAsync()
Run Code Online (Sandbox Code Playgroud)
这里的问题是(还)没有异步版本Dispose()。Dispose()所以你必须问自己——当你调用或当一个区块结束时,你期望发生什么using......?换句话说,有什么要求?
您可能需要Dispose等待所有未完成的任务,然后完成其工作。但Dispose不能使用await(它不是异步的)。它能做的最好的事情就是调用Result强制任务完成,但这将是一个阻塞调用,并且如果任何异步任务正在等待其他任务,则很容易死锁。
相反,我建议以下要求:当调用者调用 时Dispose(),该调用将标记要处置的网关,然后立即返回,因为知道处置机制将在最后一个任务完成时自行激活。
如果这个要求足够的话,这是可能的,但有点混乱。就是这样:
每次调用方法(例如Request)时,都会将返回的任务“包装”在另一个任务中,该任务包括检查调用者是否已请求释放网关。
如果已请求处置,请立即进行处置,然后再将任务标记为已完成。因此,当调用者等待任务时,它将强制进行处置。
这是我的实现。我告诉过你这很丑。
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();
}
}
Run Code Online (Sandbox Code Playgroud)
这是一些测试代码:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是输出:
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
Run Code Online (Sandbox Code Playgroud)
这是我的小提琴,如果您想尝试一下:链接
我真的不推荐这样做(如果要处理某些东西,您应该更好地控制它的生命周期),但为您编写这段代码很有趣。
注意:由于使用了引用计数,因此需要进行额外的工作才能使该解决方案成为线程安全的,或者使其能够适应网关的请求方法之一引发异常的情况。