为什么我得到 ImageSharp 不支持同步读取?

Why am I getting Synchronous reads are not supported in ImageSharp?

我正在尝试使用 Blazor 输入文件以及 Imagesharp 库将 IBrowserFile 转换为图像。
我的方法看起来像这样

public async Task<byte[]> ConvertFileToByteArrayAsync(IBrowserFile file)
        {

            using var image = Image.Load(file.OpenReadStream());
            image.Mutate(x => x.Resize(new ResizeOptions
            {
                Mode = ResizeMode.Min,
                Size = new Size(128)
            }));

            MemoryStream memoryStream = new MemoryStream();
            if (file.ContentType == "image/png")
            {

                await image.SaveAsPngAsync(memoryStream);
            }
            else
            {
                await image.SaveAsJpegAsync(memoryStream);
            }
            var byteFile = memoryStream.ToArray();
            memoryStream.Close();
            memoryStream.Dispose();


            return byteFile;
            
        } 

但我收到以下错误:

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Synchronous reads are not supported.
System.NotSupportedException: Synchronous reads are not supported.
   at Microsoft.AspNetCore.Components.Forms.BrowserFileStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.Stream.CopyTo(Stream destination, Int32 bufferSize)
   at SixLabors.ImageSharp.Image.WithSeekableStream[ValueTuple`2](Configuration configuration, Stream stream, Func`2 action)
   at SixLabors.ImageSharp.Image.Load(Configuration configuration, Stream stream, IImageFormat& format)
   at SixLabors.ImageSharp.Image.Load(Configuration configuration, Stream stream)
   at SixLabors.ImageSharp.Image.Load(Stream stream)
   at MasterMealWA.Client.Services.FileService.ConvertFileToByteArrayAsync(IBrowserFile file) in F:\CoderFoundry\Code\MasterMealWA\MasterMealWA\Client\Services\FileService.cs:line 37
   at MasterMealWA.Client.Pages.RecipePages.RecipeCreate.CreateRecipeAsync() in F:\CoderFoundry\Code\MasterMealWA\MasterMealWA\Client\Pages\RecipePages\RecipeCreate.razor:line 128
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

郑重声明,第 37 行是“使用 var 图像……”,我不太明白我在哪里使用多个流,除非它是读取流和内存流。但是,我也不知道如何关闭我用 file.OpenReadStream.

打开的流

您必须调用异步方法,例如 LoadAsyncDisposeAsync() 而不是同步 Dispose()。使用 await using xxx 等待对 DisposeAsync().

的调用
public async Task<byte[]> ConvertFileToByteArrayAsync(IBrowserFile file)
{
    await using var image = await image.LoadAsync(file.OpenReadStream());
    image.Mutate(x => x.Resize(new ResizeOptions
    {
        Mode = ResizeMode.Min,
        Size = new Size(128)
    }));

    MemoryStream memoryStream = new MemoryStream();
    if (file.ContentType == "image/png")
    {

        await image.SaveAsPngAsync(memoryStream);
    }
    else
    {
        await image.SaveAsJpegAsync(memoryStream);
    }
    var byteFile = memoryStream.ToArray();
    memoryStream.Close();
    await memoryStream.DisposeAsync();

    return byteFile;
}

Image.Load是一个同步操作。尝试使用异步版本:

using var image = await Image.LoadAsync(file.OpenReadStream());

背景:

  • ASP.NET Core 3+ 通过让所有 Stream 对象由 ASP.NET Core 处理(例如 HttpRequest.BodyIBrowserFile.OpenReadStream()) 每当调用非异步 Stream.ReadStream.Write 方法时抛出异常。

  • 此外,您不需要中间过程MemoryStream:您可以将 ImageSharp 输出直接写入响应。

正确解法:

您正在调用 ImageSharp 的 Image.Load 方法,该方法使用非异步 Stream 方法。解决方法是简单地使用 await Image.LoadAsync 代替:

因此将您的代码更改为:

// I assume this is a Controller Action method
// This method does not return an IActionResult because it writes directly to the response in the action method. See examples here: 

public async Task ResizeImageAsync( IBrowserFile file )
{
    await using( Stream stream = file.OpenReadStream() )
    using( Image image = await Image.LoadAsync( stream ) )
    {
        ResizeOptions ro = new ResizeOptions
        {
            Mode = ResizeMode.Min,
            Size = new Size(128)
        };

        image.Mutate( img => img.Resize( ro ) );

        if( file.ContentType == "image/png" ) // <-- You should not do this: *never trust* the client to be correct and truthful about uploaded files' types and contents. In this case it's just images so it's not that big a deal, but always verify independently server-side.
        {
            this.Response.ContentType = "image/png";
            await image.SaveAsPngAsync( this.Response.Body );
        }
        else
        {
            this.Response.ContentType = "image/jpeg";
            await image.SaveAsJpegAsync( this.Response.Body );
        }
}

替代(非)解决方案:拖延

只需禁用ASP.NET核心对非异步IO的禁止:

public void ConfigureServices(IServiceCollection services)
{
    // If using Kestrel:
    services.Configure<KestrelServerOptions>(options =>
    {
        options.AllowSynchronousIO = true;
    });

    // If using IIS:
    services.Configure<IISServerOptions>(options =>
    {
        options.AllowSynchronousIO = true;
    });
}