【问题标题】:Why am I getting Synchronous reads are not supported in ImageSharp?为什么 ImageSharp 不支持同步读取?
【发布时间】:2021-09-04 15:24:38
【问题描述】:

我正在尝试使用 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 image ......”,我不太清楚我在哪里使用多个流,除非它是读取流和内存流。但是,我也看不到如何关闭使用 file.OpenReadStream 打开的流。

【问题讨论】:

  • 我承认我很惊讶 ImageSharp 完全可以在 Blazor 上工作 - 我猜它使用的是 100% 托管的代码路径,而没有 P/Invoking 到其他图形库?表现如何?您可能在原始 JavaScript 和 &lt;canvas&gt; 中执行此操作会更好。
  • 无论如何,问题在于 ASP.NET Core(默认情况下)不鼓励人们在 在 ASP.NET Core 自己的流上使用非异步流方法(例如 IBrowserFile.OpenReadStream() )。要解决此问题,请改用Image.LoadAsync
  • 所以你下面的修复肯定有效,但你对性能影响也是正确的。它需要 15-30 秒来处理用户端的图像。我想我需要重新考虑一下。
  • 正如我所说,要在客户端调整图像大小只需使用&lt;canvas&gt; - 它在我所知道的所有主要浏览器中都闪电般快速和硬件加速。至于您看到的性能不佳:这正是 Blazor 的工作方式:它在 WASM 中在单个 CPU 线程中执行 CPU 密集型位图操作。尽管 Blazor 的酷炫演示给我留下了深刻的印象,但我并不认为在其中构建生产 Web 应用程序是一个好主意。
  • @HenkHolterman 我接受了它,因为它阐述得很好并解释了我哪里出错了。它只是没有用,因为我一开始就采取了错误的方法。它正确地回答了我的问题,但失败是我的,不是任何人在这里提供建议。

标签: c# asp.net blazor blazor-webassembly imagesharp


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    背景:

    正确的解决方案:

    您正在调用 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: https://stackoverflow.com/questions/42771409/how-to-stream-with-asp-net-core
    
    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 Core 对非异步 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;
        });
    }
    

    【讨论】:

    • 你真的不应该提到“拖延”选项。您可能只是说服了 OP 做正确的事情。
    • 作为记录,我没有阅读拖延选项,因为第一个选项似乎有效。非常感谢!
    • 这是不正确的。 Wasm 是个例外。这里没有asp.net Server,也没有Response,也没有Response.Stream。
    • 不过,您使用 Canvas 是对的。
    • @HenkHolterman 你是对的——但我也是对的:WASM 中的 Stream 类与服务器端流类具有相同的仅异步检查。真的……呃……酷,在如此不同的环境中看到这种保真度。
    【解决方案3】:

    您必须调用异步方法,例如 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;
    }
    

    【讨论】:

    • 这是不正确的。例外是因为 ASP.NET Core 提供了由 file.OpenReadStream() 公开的 Stream - 默认情况下 ASP.NET Core 会抛出非异步 Read/Write 调用以阻止人们使用非异步 IO。该异常与不使用DisposeAsync无关。
    • @Dai 我可能错过了同步加载,但在使用异步处理确实解决了该问题的另一种情况下,我确实收到了同样的错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-16
    • 2010-09-29
    • 2021-12-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多