0

I'm working with .Net Core 5.0 and Dapper as ORM.

I have the following c# code:

    public Task<IEnumerable<FooViewModel>> FetchAllFoos1(CancellationToken cancel = default)
    {
        string sql = "SELECT * FROM Foos";

        var context = new DbContext();
        var connection = context.GetConnection();
        var cmd = new CommandDefinition(sql, cancellationToken: cancel);

        return connection.QueryAsync<Foo>(cmd)
            .ContinueWith(x => x.Result.Select(y => ToFooViewModel(y)), cancel);
    }

This code is working perfectly.

But this one not and I don't understand why:

    public Task<IEnumerable<FooViewModel>> FetchAllFoos2(CancellationToken cancel = default)
    {
        string sql = "SELECT * FROM Foos";

        using (var context = new DbContext())
        {
            using (var connection = context.GetConnection())
            {
                 var cmd = new CommandDefinition(sql, cancellationToken: cancel);

                 return connection.QueryAsync<Foo>(cmd)
                        .ContinueWith(x => x.Result.Select(y => ToFooViewModel(y)), cancel);
            }
        }
    }

When awaiting the result of FetchAllFoos2: var result = await FetchAllFoos2(), i have a Task Cancelled exception. It happens in the ContinueWith, when it's trying to get x.Result.

I know that the issue come from because i'm using "using" that close the context/connection, but i don't undestand the inner reasons of the exception. I like to use "using" to make sure that any disposable object is cleaned when i'm over the control of the using, but it seems that i cannot use it here..

Can you help me to understand ?

Thank you.

Davit
  • 23
  • 5
  • 3
    Don't use ContinueWith, use `await` and you will not have a problem. Otherwise dont use Using and dispose in your continuation – TheGeneral Aug 21 '21 at 01:18
  • Can you explain me the reason ? – Davit Aug 21 '21 at 01:19
  • Yeah i could, but what would be easier, is to just step through the code and you will see what is happening straight away, put a break point on the end of the method, and put one in the ContinueWith, see which hits first – TheGeneral Aug 21 '21 at 01:20
  • In short, you are saying to your friend, go and get me something form the shop, and then shutting the door on them before they get back and wondering why there is an issue – TheGeneral Aug 21 '21 at 01:22
  • Ok thanks, i'll experiment this and come back if needed, thanks for the hint. – Davit Aug 21 '21 at 01:23

1 Answers1

4

As TheGeneral pointed out, the core problem is that you're using the dangerous, low-level ContinueWith method. As a general rule, use await instead of ContinueWith.

public async Task<IEnumerable<FooViewModel>> FetchAllFoos2(CancellationToken cancel = default)
{
    string sql = "SELECT * FROM Foos";

    using (var context = new DbContext())
    {
        using (var connection = context.GetConnection())
        {
             var cmd = new CommandDefinition(sql, cancellationToken: cancel);

             var result = await connection.QueryAsync<Foo>(cmd);
             return result.Select(y => ToFooViewModel(y));
        }
    }
}

One of the problems of skipping async and await is that things like disposal happen at an incorrect time. For the async method, the disposals happen after the data is retrieved (after the await). For the non-async method, the disposals happen after the query is started but (possibly) before it returns its data.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810