Skip to content

Asynchronous implementation of MemorySource<TSource>.CopyToAsync #265

Closed

Description

@OronDF343

Overview

Currently, the stream returned by AsStream() implements CopyToAsync which calls the provided CopyTo implementation, which calls destination.Write. This causes unexpected synchronous I/O.

Due to the synchronous I/O, an exception is thrown if this method is used in ASP.NET Core to write to the response stream:

System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.Stream.Write(ReadOnlySpan`1 buffer)
   at Microsoft.Toolkit.HighPerformance.Streams.MemoryStream`1.CopyTo(Stream destination, Int32 bufferSize)
   at Microsoft.Toolkit.HighPerformance.Streams.MemoryStream`1.CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken)

API breakdown

Note: These are internal APIs

In order to implement this in a performant way, I'm thinking of adding a property Memory to ISpanOwner:

internal interface ISpanOwner
{
    /// <summary>
    /// Gets a <see cref="Memory{T}"/> instance wrapping the underlying memory area.
    /// </summary>
    Memory<byte> Memory { get; }
}

And then implementing CopyToAsync like this:

partial class MemoryStream<TSource>
{
    /// <inheritdoc/>
    public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return Task.FromCanceled(cancellationToken);
        }

        try
        {
            MemoryStream.ValidateDisposed(this.disposed);

            Memory<byte> source = this.source.Memory.Slice(this.position);

            this.position += source.Length;

            return destination.WriteAsync(source, cancellationToken).AsTask();
        }
        catch (OperationCanceledException e)
        {
            return Task.FromCanceled(e.CancellationToken);
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }
}

Usage example

public async Task ProcessRequestAsync(HttpContext context)
{
    var memory = await _service.GetDataAsync();
    var stream = memory.AsStream();
    await stream.CopyToAsync(context.Response.Body);
}

Breaking change?

No

Alternatives

Not applicable

Additional context

Wasn't sure at first if this counted as a bug, upon closer inspection it looks a bit more likely, so I may have chosen the wrong template.

Help us help you

Yes, I'd like to be assigned to work on this item

Activity

  1. changed the title [-]Add `CopyToAsync` implementation to `MemorySource<TSource>`[/-] [+]Asynchronous implementation of `MemorySource<TSource>.CopyToAsync`[/+] on May 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature request 📬A request for new changes to improve functionality

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions