0

More specifically this applies only to resources which have asynchronous dependencies themselves (but I think that's majority of them).

Concrete example:

class Foo : IAsyncDisposable
{
    public ValueTask DisposeAsync { ... }
}

class Bar : IAsyncDisposable
{
    Foo _foo;
    public Bar(Foo foo)
    { 
        _foo = foo;
        // throw Exception(); 
    }

    public ValueTask DisposeAsync() { return _foo.DisposeAsync(); };
}

Can Bar be constructed synchronously? Let's try:

public void MakeBar(){
    Foo foo;
    try
    {
       foo = new Foo();
       return new Bar(foo);
    }
    catch
    {
        // cleanup foo. How?
        throw;
    }
}

We will either call foo.DisposeAsync synchronously, or we will leak resource which is not disposed. Another option is to store foo for later and call dispose in async context (that's how DI containers work), but we need to await it anyway.

My conclusion is that unless I control the object and I can guarantee that construction does not fail, the construction should be asynchronous.

Is this conclusion correct?

By asynchronous construction I mean that function which creates it returns task (or value task):

public async Task<Bar> MakeBar(){
    Foo? foo = null;
    try
    {
       foo = new Foo();
       return new Bar(foo);
    }
    catch
    { 
        await foo?.DisposeAsync();
        throw;
    }
}

7

1 Answer 1

4

Presumably MakeBar is part of a factory class, so you can dispose any unused dependencies asynchronously and await their disposal as part of the disposal of the factory.

class BarFactory : IAsyncDisposable
{
    protected readonly List<Task> _unused = new List<Task>();

    public Bar MakeBar()
    {
        var foo = new Foo();
        try
        {
            return new Bar(foo);
        }
        catch(SomeException e)
        {
            _usused.Add( foo.DisposeAsync().AsTask() );
            throw;
        }
    }

    public async ValueTask DisposeAsync()
    {
        await Task.WhenAll( _unused );
    }
}
3
  • You are not supposed to store ValueTask, you should either await it, or call AsTask or Preserve.
    – Shadow
    Commented Nov 30, 2021 at 20:32
  • That's actually approach which is taken by IOC containers. I think it's ok for IOC container to store Foo, because it could be reused by another class. In my example we want to dispose Foo as soon as possible, to not leak any resources (because we know the process failed). And storing instances for later creates new lifetime to manage.
    – Shadow
    Commented Nov 30, 2021 at 20:33
  • 1
    @Shadow My code does not store any instances for later. It disposes the object immediately and stores the task, not the object. The only thing that is deferred is the await. If you need to guarantee complete and immediate disposal, you can dispose the factory.
    – John Wu
    Commented Nov 30, 2021 at 20:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.