如何让 Dispose 等待所有异步方法?

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)


Joh*_* Wu 1

这里的问题是(还)没有异步版本Dispose()Dispose()所以你必须问自己——当你调用或当一个区块结束时,你期望发生什么using......?换句话说,有什么要求?

您可能需要Dispose等待所有未完成的任务,然后完成其工作。但Dispose不能使用await(它不是异步的)。它能做的最好的事情就是调用Result强制任务完成,但这将是一个阻塞调用,并且如果任何异步任务正在等待其他任务,则很容易死锁。

相反,我建议以下要求:当调用者调用 时Dispose(),该调用将标记要处置的网关,然后立即返回,因为知道处置机制将在最后一个任务完成时自行激活。

如果这个要求足够的话,这可能的,但有点混乱。就是这样:

  1. 每次调用方法(例如Request)时,都会将返回的任务“包装”在另一个任务中,该任务包括检查调用者是否已请求释放网关。

  2. 如果已请求处置,请立即进行处置,然后再将任务标记为已完成。因此,当调用者等待任务时,它将强制进行处置。

这是我的实现。我告诉过你这很丑。

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)

这是我的小提琴,如果您想尝试一下:链接

我真的不推荐这样做(如果要处理某些东西,您应该更好地控制它的生命周期),但为您编写这段代码很有趣。

注意:由于使用了引用计数,因此需要进行额外的工作才能使该解决方案成为线程安全的,或者使其能够适应网关的请求方法之一引发异常的情况。