【问题标题】:Increase Speed for Streaming Large(1-10 gb) files .Net Core提高流式传输大型 (1-10 gb) 文件的速度 .Net Core
【发布时间】:2020-03-20 10:51:31
【问题描述】:

我正在尝试使用 multipartform-data 通过我的 API 上传 *.iso 文件并将它们流式传输到本地文件夹。 我使用了 Stream.CopyAsync(destinationStream) ,它运行缓慢,但还不错。但现在我需要报告进度。所以我使用了自定义 CopyTOAsync 并向其添加了进度报告。但是该方法非常慢(根本不可接受),即使与 Stream::CopyToASync 相比也是如此。

 public async Task CopyToAsync(Stream source, Stream destination, long? contentLength, ICommandContext context, int bufferSize = 81920 )
    {
        var buffer = new byte[bufferSize];
        int bytesRead;
        long totalRead = 0;
        while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            await destination.WriteAsync(buffer, 0, bytesRead);
            totalRead += bytesRead;
            context.Report(CommandResources.RID_IMAGE_LOADIND, Math.Clamp((uint)((totalRead * 100) / contentLength), 3, 99));
        }
        _logger.Info($"Total read during upload : {totalRead}");
    }

我尝试了什么: Stream::CopyToAsync 的默认缓冲区大小为 81920 字节,我先使用相同的值,然后尝试将缓冲区大小增加到 104857600 字节 - 没有区别。

您对如何提高自定义 CopyToAsync 的性能还有其他想法吗?

【问题讨论】:

  • 那些文件是什么?您可以使用压缩流传输未压缩的文件。
  • 传输一个 1GB 的文件需要多长时间?您是否将这些文件上传到远程服务器?还是一切都保留在您的本地网络上?
  • 我不想告诉你,但你的方法一开始就有问题。想要更快的性能?不要发送 MULTIPARTFORM-DATA - 那里的编码使您的数据开始时大 30%,因为它必须是 ASCII 编码的。将它们作为二进制内容以 serate 请求(无形式)流式传输。在(获取上传网址)之前或作为标题发送必要的元数据 aeitehr。看看 Youtube 在他们的 SDK 中是如何做到的。
  • @ChristophLütjen 我怀疑ArrayPool 是否有助于考虑到有问题的代码不是在紧密循环中分配数组。

标签: c# asp.net-core stream multipartform-data large-files


【解决方案1】:
  • 始终使用 ConfigureAwaitawait 来指定异步延续的线程同步。
    • 根据平台,省略 ConfigureAwait 可能默认与 UI 线程(WPF、WinForms)或任何线程(ASP.NET Core)同步。如果它与 Stream 复制操作中的 UI 线程同步,那么性能会大幅下降也就不足为奇了。
    • 如果您在线程同步的上下文中运行代码,那么您的 await 语句将被不必要地延迟,因为程序将继续调度到可能很忙的线程。
  • 使用大小至少为几百 KiB 的缓冲区 - 甚至是兆字节大小的缓冲区用于异步操作 - 而不是典型的 4KiB 或 80KiB 大小的数组。
  • 如果您使用的是FileStream,请确保您使用了FileOptions.AsynchronoususeAsync: true,否则FileStream伪造其异步操作,方法是使用线程池线程而不是执行阻塞IO Windows 的原生异步 IO。

关于您的实际代码 - 只需使用 Stream::CopyToAsync 而不是自己重新实现它。如果您想要进度报告,请考虑将Stream 子类化(作为代理包装器)。

我会这样写你的代码:

  1. First, add my ProxyStream class from this GitHub Gist 给你的项目。
  2. 然后继承 ProxyStream 以添加对 IProgress 的支持:
  3. 确保使用FileOptions.Asynchronous | FileOptions.SequentialScan 创建任何FileStream 实例。
  4. 使用CopyToAsync
public class ProgressProxyStream : ProxyStream
{
    private readonly IProgress<(Int64 soFar, Int64? total)> progress;
    private readonly Int64? total;

    public ProgressProxyStream( Stream stream, IProgress<Int64> progress, Boolean leaveOpen )
        : base( stream, leaveOpen ) 
    {
        this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
        this.total = stream.CanSeek ? stream.Length : (Int64?)null;
    }

    public override Task<Int32> ReadAsync( Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken )
    {
        this.progress.Report( ( offset, this.total ) );
        return this.Stream.ReadAsync( buffer, offset, count, cancellationToken );
    }
}

如果上述ProgressProxyStream 的性能仍然受到影响,那么我愿意打赌瓶颈在IProgress.Report 回调目标内(我假设它与UI 线程同步)-in which case a better solution is to use a (System.Threading.Channels.Channel) 用于ProgressProxyStream (甚至是您对 IProgress&lt;T&gt; 的实现)以将进度报告转储到而不阻塞任何其他 IO 活动。

【讨论】:

  • 这是一个很好的建议,但鉴于framework's own implementation 似乎没有考虑到这一点,它并不能真正解释“慢但还不错”与“非常慢”的区别 OP添加进度报告部分后观察。他们还尝试了合理增加缓冲区大小。
  • @CeeMcSharpface 我敢打赌,context.Report 调用内部发生了阻塞线程同步。
  • @Dai,非常感谢。瓶颈确实在 Report 方法中。感谢您的帮助。
猜你喜欢
  • 2013-05-27
  • 2021-07-30
  • 2017-12-29
  • 2019-06-11
  • 1970-01-01
  • 2012-03-05
  • 2017-11-22
  • 2021-08-29
  • 2019-04-28
相关资源
最近更新 更多