Search code examples
c#asynchronousdesign-patternsstream

Writing an async version of a method in a DRY fashion?


The documentation of System.IO.Stream specifies that, when deriving from it, at most, only the Read method needs to be implemented.

This automatically enables the use of ReadAsync method in the derived type, i.e. you get asynchronous reading for free.

Looking at how it is implemented, it is pretty involved. There is a custom class (ReadWriteTask) deriving from Task<int> which is responsible for the asynchronous reading.

A naive approach for one to avoid DRY could be the following:

public abstract class Reader
{
    public abstract int Length { get; }

    public bool TryRead(Stream stream, byte[] buffer)
    {
        if (stream.Length - stream.Position < Length)
        {
            return false;
        }

        stream.ReadExactly(buffer);

        // do some special, lengthy stuff

        return true;
    }

    public async Task<bool> TryReadAsync(Stream stream, byte[] buffer)
    {
        return await Task.Run(() => TryRead(stream, buffer)).ConfigureAwait(false);
    }
}

However, after looking at their implementation, I have the feeling that the above approach may be wrong/inefficient.

Can you suggest a pattern to tackle this problem?


Solution

  • This automatically enables the use of ReadAsync method in the derived type, i.e. you get asynchronous reading for free.

    Not really. The asynchronous reading you get is fake-async, meaning it's just a blocking read done on a thread pool thread. So it appears asynchronous to the calling code, but it's just blocking a different thread.

    Ideally, if you have a Stream that does real I/O, you should override ReadAsync and friends so that they are truly asynchronous implementations. And indeed the derived types in the framework do do this.

    Looking at how it is implemented, it is pretty involved. There is a custom class (ReadWriteTask) deriving from Task which is responsible for the asynchronous reading.

    ReadWriteTask is more about reducing/consolidating allocations than it is "responsible for the asynchronous reading". The delegate passed to that class (which is executed on a thread pool thread) is what actually does the reading.

    Side note: the modern .NET runtime code lives here.

    A naive approach for one to avoid DRY could be the following: [async over sync code]

    However, after looking at their implementation, I have the feeling that the above approach may be wrong/inefficient.

    It's practically the same thing as the Stream.ReadAsync base implementation: toss the synchronous code on a thread pool thread. And yes, both your code and the Stream.ReadAsync base implementation are inefficient.

    Can you suggest a pattern to tackle this problem?

    My personal preference is the boolean argument hack; a more modern version would be to use generic code generation (as I describe on my blog).