1

I am looking at the code for DbDataReader (also DbCommand) at MS reference and can't figure out what is async in ReadAsync() method.

    virtual public Task<bool> ReadAsync(CancellationToken cancellationToken) {
        if (cancellationToken.IsCancellationRequested) {
            return ADP.CreatedTaskWithCancellation<bool>();
        }
        else {
            try {
                return Read() ? ADP.TrueTask : ADP.FalseTask;
            }
            catch (Exception e) {
                return ADP.CreatedTaskWithException<bool>(e);
            }
        }
    }

The ReadAsync method just calls Read method and returns a complete task. Doesn't this block the calling thread the same way as calling Read directly?

I have noticed the same pattern in DbCommand ExecuteReaderAsync and other methods. They just call the sync versions and return completed Tasks.

What am I missing here?

UPDATE: I did not miss anything, as @PeterBons explained nicely (also in the documentation). I still don't like it, but that's my problem.

gajo357
  • 958
  • 12
  • 22
  • 2
    Several providers (including popular ones) do not support real asynchronous communication with their database, so they just inherit this implementation. Those who does - override and provide real implementation. Yes those methods could be made abstract, but another design decision was made to not force providers which do not support this to provide their own bogus implementations. – Evk Nov 10 '17 at 21:18

1 Answers1

4

You are looking at a virtual method in an abstract class. If you want (future) implementations to be able to do some truly async work you will have to define a method signature that allows that. So it should return a Task or Task<T>. Remember that just using a Task does not make anything asynchronous, it makes it awaitable.

The use of the Task<bool> return type in this example virtual method is to facilitate other classes that derive from DbDataReader to provide real async behavior in their implementation of ReadAsync.

For example, a truly async implementation could do something like

class TrueAsyncReader : DbDataReader
{
    ...

    public override async Task<bool> ReadAsync(CancellationToken cancellationToken) 
    {
        ...

        return await ReadFromDbAsync();
    }
}

As you can see you can now have async and non async implementations, without having to alter the method signature.

Since you can easily call synchronous code from an async method this is the way to go. Calling async code from a synchronous method is a nog go.

for non async implementations that need to return a task you can return something like Task.FromResult<T> or Task.CompletedTask. This will not block.

See also await Task.CompletedTask for what?

To summarize: the default implementation does not do anything async but derived classes can without having to change the method signature.

Peter Bons
  • 26,826
  • 4
  • 50
  • 74
  • The class is abstract, but the method isn't. You can cast your actual object to DbDataReader and call this method. My question is what does it give me, is it truly async? – gajo357 Nov 10 '17 at 20:14
  • 3
    @gajo357 the method is virtual and so can be overridden in an implementation. The default implementation is not async, any override method can be async. The code that you show in the question is run synchronously, there is nothing running async in it. Remember that just using a `Task` does not make anything asynchronous. – Peter Bons Nov 10 '17 at 20:19
  • 2
    There is a big difference between a method being async, and a method being awaitable. I feel that is worth editing in here. – Gusdor Nov 10 '17 at 20:39
  • @Peter Bons Thanks. I wasn't sure if I had missed or misunderstood something. I still don't know why is it there at all. It has a bogus implementation, it's not in the IDbDataReader interface. It looks very misleading. – gajo357 Nov 10 '17 at 20:48
  • @gajo357 it is there to facilitate other classes that derive from DbDataReader to provide real async behavior in their implementation of ReadAsync. – Peter Bons Nov 10 '17 at 21:28
  • @PeterBons I would throw NotImplementedException here, because you never want to use this one. When I see ReadAsync in the API, I have expectations. If it's not implemented, I'll call the sync version myself. But if it is implemented, I expect it to do what it says it does. – gajo357 Nov 11 '17 at 09:09
  • You should not, let me explain. Say you do call the sync version yourself and later on you decide to switch to a true async implementation of DbDataReader. You then have to refactor all your calls to the sync version to the async version of the methods to make it effective. – Peter Bons Nov 11 '17 at 13:16
  • That's exactly what I want to do. If I use some library, I don't want that library to cheat me. Library says ReadAsync(), while in reality it is Read(). – gajo357 Nov 11 '17 at 14:46
  • But you did answer my original question. Thank you – gajo357 Nov 11 '17 at 14:55